noVNC console integration (#3967)
* Adding noVNC repo
* Adding support for noVNC
* Adding Ctl+Esc
* Removing device name from novnc header
diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java
index 8496301..875bbc5 100644
--- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java
+++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java
@@ -19,6 +19,8 @@
import com.cloud.utils.component.Manager;
import com.cloud.vm.ConsoleProxyVO;
+import org.apache.cloudstack.framework.config.ConfigKey;
+
public interface ConsoleProxyManager extends Manager, ConsoleProxyService {
public static final int DEFAULT_PROXY_CAPACITY = 50;
@@ -31,9 +33,14 @@
public static final int DEFAULT_PROXY_URL_PORT = 80;
public static final int DEFAULT_PROXY_SESSION_TIMEOUT = 300000; // 5 minutes
+ public static final int DEFAULT_NOVNC_PORT = 8080;
+
public static final String ALERT_SUBJECT = "proxy-alert";
public static final String CERTIFICATE_NAME = "CPVMCertificate";
+ public static final ConfigKey<Boolean> NoVncConsoleDefault = new ConfigKey<Boolean>("Advanced", Boolean.class, "novnc.console.default", "true",
+ "If true, noVNC console will be default console for virtual machines", true);
+
public void setManagementState(ConsoleProxyManagementState state);
public ConsoleProxyManagementState getManagementState();
diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
index 368fc33..8638fb5 100644
--- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
+++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
@@ -32,6 +32,8 @@
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
@@ -154,7 +156,8 @@
// Starting, HA, Migrating, Running state are all counted as "Open" for available capacity calculation
// because sooner or later, it will be driven into Running state
//
-public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxyManager, VirtualMachineGuru, SystemVmLoadScanHandler<Long>, ResourceStateAdapter {
+public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxyManager, VirtualMachineGuru, SystemVmLoadScanHandler<Long>, ResourceStateAdapter, Configurable {
+
private static final Logger s_logger = Logger.getLogger(ConsoleProxyManagerImpl.class);
private static final int DEFAULT_CAPACITY_SCAN_INTERVAL = 30000; // 30 seconds
@@ -1741,4 +1744,14 @@
_consoleProxyAllocators = consoleProxyAllocators;
}
+ @Override
+ public String getConfigComponentName() {
+ return ConsoleProxyManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey<?>[] getConfigKeys() {
+ return new ConfigKey<?>[] { NoVncConsoleDefault };
+ }
+
}
diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java
index ae9b5c5..ed73625 100644
--- a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java
+++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java
@@ -41,6 +41,12 @@
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
+
+import com.cloud.vm.VmDetailConstants;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import com.cloud.consoleproxy.ConsoleProxyManager;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor;
@@ -59,10 +65,7 @@
import com.cloud.vm.UserVmDetailVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
-import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.UserVmDetailsDao;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
/**
* Thumbnail access : /console?cmd=thumbnail&vm=xxx&w=xxx&h=xxx
@@ -478,7 +481,12 @@
param.setClientTunnelSession(parsedHostInfo.third());
}
- sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
+ if (param.getHypervHost() != null || !ConsoleProxyManager.NoVncConsoleDefault.value()) {
+ sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
+ } else {
+ sb.append("/resource/noVNC/vnc_lite.html?port=" + ConsoleProxyManager.DEFAULT_NOVNC_PORT + "&token="
+ + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
+ }
// for console access, we need guest OS type to help implement keyboard
long guestOs = vm.getGuestOSId();
diff --git a/services/console-proxy/server/pom.xml b/services/console-proxy/server/pom.xml
index 4bc6593..2d43ebf 100644
--- a/services/console-proxy/server/pom.xml
+++ b/services/console-proxy/server/pom.xml
@@ -50,6 +50,21 @@
<artifactId>cloudstack-service-console-proxy-rdpclient</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${cs.jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-server</artifactId>
+ <version>${cs.jetty.version}</version>
+ </dependency>
</dependencies>
<build>
<resources>
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java
index 2161de2..7a70a38 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java
@@ -32,6 +32,7 @@
import java.util.concurrent.Executor;
import org.apache.log4j.xml.DOMConfigurator;
+import org.eclipse.jetty.websocket.api.Session;
import com.cloud.consoleproxy.util.Logger;
import com.cloud.utils.PropertiesUtil;
@@ -344,12 +345,22 @@
server.createContext("/ajaximg", new ConsoleProxyAjaxImageHandler());
server.setExecutor(new ThreadExecutor()); // creates a default executor
server.start();
+
+ ConsoleProxyNoVNCServer noVNCServer = getNoVNCServer();
+ noVNCServer.start();
+
} catch (Exception e) {
s_logger.error(e.getMessage(), e);
System.exit(1);
}
}
+ private static ConsoleProxyNoVNCServer getNoVNCServer() {
+ if (httpListenPort == 443)
+ return new ConsoleProxyNoVNCServer(ksBits, ksPassword);
+ return new ConsoleProxyNoVNCServer();
+ }
+
private static void startupHttpCmdPort() {
try {
s_logger.info("Listening for HTTP CMDs on port " + httpCmdListenPort);
@@ -395,7 +406,7 @@
String clientKey = param.getClientMapKey();
synchronized (connectionMap) {
viewer = connectionMap.get(clientKey);
- if (viewer == null) {
+ if (viewer == null || viewer.getClass() == ConsoleProxyNoVncClient.class) {
viewer = getClient(param);
viewer.initClient(param);
connectionMap.put(clientKey, viewer);
@@ -429,7 +440,7 @@
String clientKey = param.getClientMapKey();
synchronized (connectionMap) {
ConsoleProxyClient viewer = connectionMap.get(clientKey);
- if (viewer == null) {
+ if (viewer == null || viewer.getClass() == ConsoleProxyNoVncClient.class) {
authenticationExternally(param);
viewer = getClient(param);
viewer.initClient(param);
@@ -521,4 +532,40 @@
new Thread(r).start();
}
}
+
+ public static ConsoleProxyNoVncClient getNoVncViewer(ConsoleProxyClientParam param, String ajaxSession,
+ Session session) throws AuthenticationException {
+ boolean reportLoadChange = false;
+ String clientKey = param.getClientMapKey();
+ synchronized (connectionMap) {
+ ConsoleProxyClient viewer = connectionMap.get(clientKey);
+ if (viewer == null || viewer.getClass() != ConsoleProxyNoVncClient.class) {
+ authenticationExternally(param);
+ viewer = new ConsoleProxyNoVncClient(session);
+ viewer.initClient(param);
+
+ connectionMap.put(clientKey, viewer);
+ reportLoadChange = true;
+ } else {
+ if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() ||
+ !param.getClientHostPassword().equals(viewer.getClientHostPassword()))
+ throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid");
+
+ if (!viewer.isFrontEndAlive()) {
+ authenticationExternally(param);
+ viewer.initClient(param);
+ reportLoadChange = true;
+ }
+ }
+
+ if (reportLoadChange) {
+ ConsoleProxyClientStatsCollector statsCollector = getStatsCollector();
+ String loadInfo = statsCollector.getStatsReport();
+ reportLoadInfo(loadInfo);
+ if (s_logger.isDebugEnabled())
+ s_logger.debug("Report load change : " + loadInfo);
+ }
+ return (ConsoleProxyNoVncClient)viewer;
+ }
+ }
}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java
new file mode 100644
index 0000000..349d984
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java
@@ -0,0 +1,145 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.cloud.consoleproxy.util.Logger;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.server.WebSocketHandler;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+@WebSocket
+public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
+
+ private ConsoleProxyNoVncClient viewer;
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyNoVNCHandler.class);
+
+ public ConsoleProxyNoVNCHandler() {
+ super();
+ }
+
+ @Override
+ public void configure(WebSocketServletFactory webSocketServletFactory) {
+ webSocketServletFactory.register(ConsoleProxyNoVNCHandler.class);
+ }
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException {
+
+ if (this.getWebSocketFactory().isUpgradeRequest(request, response)) {
+ response.addHeader("Sec-WebSocket-Protocol", "binary");
+ if (this.getWebSocketFactory().acceptWebSocket(request, response)) {
+ baseRequest.setHandled(true);
+ return;
+ }
+
+ if (response.isCommitted()) {
+ return;
+ }
+ }
+
+ super.handle(target, baseRequest, request, response);
+ }
+
+ @OnWebSocketConnect
+ public void onConnect(final Session session) throws IOException, InterruptedException {
+
+ String queries = session.getUpgradeRequest().getQueryString();
+ Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries);
+
+ String host = queryMap.get("host");
+ String portStr = queryMap.get("port");
+ String sid = queryMap.get("sid");
+ String tag = queryMap.get("tag");
+ String ticket = queryMap.get("ticket");
+ String ajaxSessionIdStr = queryMap.get("sess");
+ String console_url = queryMap.get("consoleurl");
+ String console_host_session = queryMap.get("sessionref");
+ String vm_locale = queryMap.get("locale");
+ String hypervHost = queryMap.get("hypervHost");
+ String username = queryMap.get("username");
+ String password = queryMap.get("password");
+
+ if (tag == null)
+ tag = "";
+
+ long ajaxSessionId = 0;
+ int port;
+
+ if (host == null || portStr == null || sid == null)
+ throw new IllegalArgumentException();
+
+ try {
+ port = Integer.parseInt(portStr);
+ } catch (NumberFormatException e) {
+ s_logger.warn("Invalid number parameter in query string: " + portStr);
+ throw new IllegalArgumentException(e);
+ }
+
+ if (ajaxSessionIdStr != null) {
+ try {
+ ajaxSessionId = Long.parseLong(ajaxSessionIdStr);
+ } catch (NumberFormatException e) {
+ s_logger.warn("Invalid number parameter in query string: " + ajaxSessionIdStr);
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ try {
+ ConsoleProxyClientParam param = new ConsoleProxyClientParam();
+ param.setClientHostAddress(host);
+ param.setClientHostPort(port);
+ param.setClientHostPassword(sid);
+ param.setClientTag(tag);
+ param.setTicket(ticket);
+ param.setClientTunnelUrl(console_url);
+ param.setClientTunnelSession(console_host_session);
+ param.setLocale(vm_locale);
+ param.setHypervHost(hypervHost);
+ param.setUsername(username);
+ param.setPassword(password);
+ viewer = ConsoleProxy.getNoVncViewer(param, ajaxSessionIdStr, session);
+ } catch (Exception e) {
+ s_logger.warn("Failed to create viewer due to " + e.getMessage(), e);
+ return;
+ }
+ }
+
+ @OnWebSocketClose
+ public void onClose(Session session, int statusCode, String reason) throws IOException, InterruptedException {
+ ConsoleProxy.removeViewer(viewer);
+ }
+
+ @OnWebSocketFrame
+ public void onFrame(Frame f) throws IOException {
+ viewer.sendClientFrame(f);
+ }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCServer.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCServer.java
new file mode 100644
index 0000000..28d179b
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCServer.java
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyStore;
+
+import com.cloud.consoleproxy.util.Logger;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class ConsoleProxyNoVNCServer {
+
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyNoVNCServer.class);
+ private static final int wsPort = 8080;
+
+ private Server server;
+
+ public ConsoleProxyNoVNCServer() {
+ this.server = new Server(wsPort);
+ ConsoleProxyNoVNCHandler handler = new ConsoleProxyNoVNCHandler();
+ this.server.setHandler(handler);
+ }
+
+ public ConsoleProxyNoVNCServer(byte[] ksBits, String ksPassword) {
+ this.server = new Server();
+ ConsoleProxyNoVNCHandler handler = new ConsoleProxyNoVNCHandler();
+ this.server.setHandler(handler);
+
+ try {
+ final HttpConfiguration httpConfig = new HttpConfiguration();
+ httpConfig.setSecureScheme("https");
+ httpConfig.setSecurePort(wsPort);
+
+ final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
+ httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+ final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ char[] passphrase = ksPassword != null ? ksPassword.toCharArray() : null;
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(new ByteArrayInputStream(ksBits), passphrase);
+ sslContextFactory.setKeyStore(ks);
+ sslContextFactory.setKeyStorePassword(ksPassword);
+ sslContextFactory.setKeyManagerPassword(ksPassword);
+
+ final ServerConnector sslConnector = new ServerConnector(server,
+ new SslConnectionFactory(sslContextFactory, "http/1.1"),
+ new HttpConnectionFactory(httpsConfig));
+ sslConnector.setPort(wsPort);
+ server.addConnector(sslConnector);
+ } catch (Exception e) {
+ s_logger.error("Unable to secure server due to exception ", e);
+ }
+ }
+
+ public void start() throws Exception {
+ this.server.start();
+ }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java
new file mode 100644
index 0000000..97963f8
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java
@@ -0,0 +1,238 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import org.apache.log4j.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+
+import java.awt.Image;
+import java.io.IOException;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import com.cloud.consoleproxy.vnc.NoVncClient;
+
+public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyNoVncClient.class);
+ private static int nextClientId = 0;
+
+ private NoVncClient client;
+ private Session session;
+
+ protected int clientId = getNextClientId();
+ protected long ajaxSessionId = 0;
+
+ protected long createTime = System.currentTimeMillis();
+ protected long lastFrontEndActivityTime = System.currentTimeMillis();
+
+ private boolean connectionAlive;
+
+ private ConsoleProxyClientParam clientParam;
+
+ public ConsoleProxyNoVncClient(Session session) {
+ this.session = session;
+ }
+
+ private int getNextClientId() {
+ return ++nextClientId;
+ }
+
+ @Override
+ public void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers) {
+ }
+
+ @Override
+ public void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers) {
+ }
+
+ @Override
+ public boolean isHostConnected() {
+ return connectionAlive;
+ }
+
+ @Override
+ public boolean isFrontEndAlive() {
+ if (!connectionAlive || System.currentTimeMillis()
+ - getClientLastFrontEndActivityTime() > ConsoleProxy.VIEWER_LINGER_SECONDS * 1000) {
+ s_logger.info("Front end has been idle for too long");
+ return false;
+ }
+ return true;
+ }
+
+ public void sendClientFrame(Frame f) throws IOException {
+ byte[] data = new byte[f.getPayloadLength()];
+ f.getPayload().get(data);
+ client.write(data);
+ }
+
+ @Override
+ public void initClient(ConsoleProxyClientParam param) {
+ setClientParam(param);
+ client = new NoVncClient();
+ connectionAlive = true;
+
+ updateFrontEndActivityTime();
+ Thread worker = new Thread(new Runnable() {
+ public void run() {
+ try {
+
+ String tunnelUrl = param.getClientTunnelUrl();
+ String tunnelSession = param.getClientTunnelSession();
+
+ try {
+ if (tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null
+ && !tunnelSession.isEmpty()) {
+ URI uri = new URI(tunnelUrl);
+ s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: "
+ + tunnelSession);
+
+ ConsoleProxy.ensureRoute(uri.getHost());
+ client.connectTo(uri.getHost(), uri.getPort(), uri.getPath() + "?" + uri.getQuery(),
+ tunnelSession, "https".equalsIgnoreCase(uri.getScheme()));
+ } else {
+ s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: "
+ + getClientHostPort());
+ ConsoleProxy.ensureRoute(getClientHostAddress());
+ client.connectTo(getClientHostAddress(), getClientHostPort());
+ }
+ } catch (UnknownHostException e) {
+ s_logger.error("Unexpected exception", e);
+ } catch (IOException e) {
+ s_logger.error("Unexpected exception", e);
+ } catch (Throwable e) {
+ s_logger.error("Unexpected exception", e);
+ }
+
+ String ver = client.handshake();
+ session.getRemote().sendBytes(ByteBuffer.wrap(ver.getBytes(), 0, ver.length()));
+
+ byte[] b = client.authenticate(getClientHostPassword());
+ session.getRemote().sendBytes(ByteBuffer.wrap(b, 0, 4));
+
+ int readBytes;
+ while (connectionAlive) {
+ b = new byte[100];
+ readBytes = client.read(b);
+ if (readBytes == -1) {
+ break;
+ }
+ if (readBytes > 0) {
+ session.getRemote().sendBytes(ByteBuffer.wrap(b, 0, readBytes));
+ updateFrontEndActivityTime();
+ }
+ }
+ connectionAlive = false;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ });
+ worker.start();
+ }
+
+ private void setClientParam(ConsoleProxyClientParam param) {
+ this.clientParam = param;
+ }
+
+ @Override
+ public void closeClient() {
+ this.connectionAlive = false;
+ ConsoleProxy.removeViewer(this);
+ }
+
+ @Override
+ public int getClientId() {
+ return this.clientId;
+ }
+
+ @Override
+ public long getAjaxSessionId() {
+ return this.ajaxSessionId;
+ }
+
+ @Override
+ public AjaxFIFOImageCache getAjaxImageCache() {
+ // Unimplemented
+ return null;
+ }
+
+ @Override
+ public Image getClientScaledImage(int width, int height) {
+ // Unimplemented
+ return null;
+ }
+
+ @Override
+ public String onAjaxClientStart(String title, List<String> languages, String guest) {
+ // Unimplemented
+ return null;
+ }
+
+ @Override
+ public String onAjaxClientUpdate() {
+ // Unimplemented
+ return null;
+ }
+
+ @Override
+ public String onAjaxClientKickoff() {
+ // Unimplemented
+ return null;
+ }
+
+ @Override
+ public long getClientCreateTime() {
+ return createTime;
+ }
+
+ public void updateFrontEndActivityTime() {
+ lastFrontEndActivityTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public long getClientLastFrontEndActivityTime() {
+ return lastFrontEndActivityTime;
+ }
+
+ @Override
+ public String getClientHostAddress() {
+ return clientParam.getClientHostAddress();
+ }
+
+ @Override
+ public int getClientHostPort() {
+ return clientParam.getClientHostPort();
+ }
+
+ @Override
+ public String getClientHostPassword() {
+ return clientParam.getClientHostPassword();
+ }
+
+ @Override
+ public String getClientTag() {
+ if (clientParam.getClientTag() != null)
+ return clientParam.getClientTag();
+ return "";
+ }
+
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
index 8659120..d5dbe08 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
@@ -54,6 +54,7 @@
s_validResourceFolders.put("js", "");
s_validResourceFolders.put("css", "");
s_validResourceFolders.put("html", "");
+ s_validResourceFolders.put("noVNC", "");
}
public ConsoleProxyResourceHandler() {
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java
new file mode 100644
index 0000000..9a43725
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java
@@ -0,0 +1,219 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.security.spec.KeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.cloud.consoleproxy.util.RawHTTP;
+
+public class NoVncClient {
+ private static final Logger s_logger = Logger.getLogger(NoVncClient.class);
+
+ private Socket socket;
+ private DataInputStream is;
+ private DataOutputStream os;
+
+ public NoVncClient() {
+ }
+
+ public void connectTo(String host, int port, String path, String session, boolean useSSL) throws UnknownHostException, IOException {
+ if (port < 0) {
+ if (useSSL)
+ port = 443;
+ else
+ port = 80;
+ }
+
+ RawHTTP tunnel = new RawHTTP("CONNECT", host, port, path, session, useSSL);
+ socket = tunnel.connect();
+ setStreams();
+ }
+
+ public void connectTo(String host, int port) throws UnknownHostException, IOException {
+ // Connect to server
+ s_logger.info("Connecting to VNC server " + host + ":" + port + "...");
+ socket = new Socket(host, port);
+ setStreams();
+ }
+
+ private void setStreams() throws IOException {
+ this.is = new DataInputStream(this.socket.getInputStream());
+ this.os = new DataOutputStream(this.socket.getOutputStream());
+ }
+
+ /**
+ * Handshake with VNC server.
+ */
+ public String handshake() throws IOException {
+
+ // Read protocol version
+ byte[] buf = new byte[12];
+ is.readFully(buf);
+ String rfbProtocol = new String(buf);
+
+ // Server should use RFB protocol 3.x
+ if (!rfbProtocol.contains(RfbConstants.RFB_PROTOCOL_VERSION_MAJOR)) {
+ s_logger.error("Cannot handshake with VNC server. Unsupported protocol version: \"" + rfbProtocol + "\".");
+ throw new RuntimeException(
+ "Cannot handshake with VNC server. Unsupported protocol version: \"" + rfbProtocol + "\".");
+ }
+
+ // Proxy that we support RFB 3.3 only
+ return RfbConstants.RFB_PROTOCOL_VERSION + "\n";
+ }
+
+ /**
+ * VNC authentication.
+ */
+ public byte[] authenticate(String password)
+ throws IOException {
+ // Read security type
+ int authType = is.readInt();
+
+ switch (authType) {
+ case RfbConstants.CONNECTION_FAILED: {
+ // Server forbids to connect. Read reason and throw exception
+ int length = is.readInt();
+ byte[] buf = new byte[length];
+ is.readFully(buf);
+ String reason = new String(buf, RfbConstants.CHARSET);
+
+ s_logger.error("Authentication to VNC server is failed. Reason: " + reason);
+ throw new RuntimeException("Authentication to VNC server is failed. Reason: " + reason);
+ }
+
+ case RfbConstants.NO_AUTH: {
+ // Client can connect without authorization. Nothing to do.
+ break;
+ }
+
+ case RfbConstants.VNC_AUTH: {
+ s_logger.info("VNC server requires password authentication");
+ doVncAuth(is, os, password);
+ break;
+ }
+
+ default:
+ s_logger.error("Unsupported VNC protocol authorization scheme, scheme code: " + authType + ".");
+ throw new RuntimeException(
+ "Unsupported VNC protocol authorization scheme, scheme code: " + authType + ".");
+ }
+ // Since we've taken care of the auth, we tell the client that there's no auth
+ // going on
+ return new byte[] { 0, 0, 0, 1 };
+ }
+
+ /**
+ * Encode client password and send it to server.
+ */
+ private void doVncAuth(DataInputStream in, DataOutputStream out, String password) throws IOException {
+
+ // Read challenge
+ byte[] challenge = new byte[16];
+ in.readFully(challenge);
+
+ // Encode challenge with password
+ byte[] response;
+ try {
+ response = encodePassword(challenge, password);
+ } catch (Exception e) {
+ s_logger.error("Cannot encrypt client password to send to server: " + e.getMessage());
+ throw new RuntimeException("Cannot encrypt client password to send to server: " + e.getMessage());
+ }
+
+ // Send encoded challenge
+ out.write(response);
+ out.flush();
+
+ // Read security result
+ int authResult = in.readInt();
+
+ switch (authResult) {
+ case RfbConstants.VNC_AUTH_OK: {
+ // Nothing to do
+ break;
+ }
+
+ case RfbConstants.VNC_AUTH_TOO_MANY:
+ s_logger.error("Connection to VNC server failed: too many wrong attempts.");
+ throw new RuntimeException("Connection to VNC server failed: too many wrong attempts.");
+
+ case RfbConstants.VNC_AUTH_FAILED:
+ s_logger.error("Connection to VNC server failed: wrong password.");
+ throw new RuntimeException("Connection to VNC server failed: wrong password.");
+
+ default:
+ s_logger.error("Connection to VNC server failed, reason code: " + authResult);
+ throw new RuntimeException("Connection to VNC server failed, reason code: " + authResult);
+ }
+ }
+
+ private byte flipByte(byte b) {
+ int b1_8 = (b & 0x1) << 7;
+ int b2_7 = (b & 0x2) << 5;
+ int b3_6 = (b & 0x4) << 3;
+ int b4_5 = (b & 0x8) << 1;
+ int b5_4 = (b & 0x10) >>> 1;
+ int b6_3 = (b & 0x20) >>> 3;
+ int b7_2 = (b & 0x40) >>> 5;
+ int b8_1 = (b & 0x80) >>> 7;
+ byte c = (byte) (b1_8 | b2_7 | b3_6 | b4_5 | b5_4 | b6_3 | b7_2 | b8_1);
+ return c;
+ }
+
+ public byte[] encodePassword(byte[] challenge, String password) throws Exception {
+ // VNC password consist of up to eight ASCII characters.
+ byte[] key = { 0, 0, 0, 0, 0, 0, 0, 0 }; // Padding
+ byte[] passwordAsciiBytes = password.getBytes(Charset.availableCharsets().get("US-ASCII"));
+ System.arraycopy(passwordAsciiBytes, 0, key, 0, Math.min(password.length(), 8));
+
+ // Flip bytes (reverse bits) in key
+ for (int i = 0; i < key.length; i++) {
+ key[i] = flipByte(key[i]);
+ }
+
+ KeySpec desKeySpec = new DESKeySpec(key);
+ SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
+ SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
+ Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+
+ byte[] response = cipher.doFinal(challenge);
+ return response;
+ }
+
+ public int read(byte[] b) throws IOException {
+ return is.read(b);
+ }
+
+ public void write(byte[] b) throws IOException {
+ os.write(b);
+ }
+
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/.eslintignore b/systemvm/agent/noVNC/.eslintignore
new file mode 100644
index 0000000..d381628
--- /dev/null
+++ b/systemvm/agent/noVNC/.eslintignore
@@ -0,0 +1 @@
+**/xtscancodes.js
diff --git a/systemvm/agent/noVNC/.eslintrc b/systemvm/agent/noVNC/.eslintrc
new file mode 100644
index 0000000..900a718
--- /dev/null
+++ b/systemvm/agent/noVNC/.eslintrc
@@ -0,0 +1,48 @@
+{
+ "env": {
+ "browser": true,
+ "es6": true
+ },
+ "parserOptions": {
+ "sourceType": "module"
+ },
+ "extends": "eslint:recommended",
+ "rules": {
+ // Unsafe or confusing stuff that we forbid
+
+ "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }],
+ "no-constant-condition": ["error", { "checkLoops": false }],
+ "no-var": "error",
+ "no-useless-constructor": "error",
+ "object-shorthand": ["error", "methods", { "avoidQuotes": true }],
+ "prefer-arrow-callback": "error",
+ "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ],
+ "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }],
+ "arrow-spacing": ["error"],
+ "no-confusing-arrow": ["error", { "allowParens": true }],
+
+ // Enforced coding style
+
+ "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
+ "indent": ["error", 4, { "SwitchCase": 1,
+ "CallExpression": { "arguments": "first" },
+ "ArrayExpression": "first",
+ "ObjectExpression": "first",
+ "ignoreComments": true }],
+ "comma-spacing": ["error"],
+ "comma-style": ["error"],
+ "curly": ["error", "multi-line"],
+ "func-call-spacing": ["error"],
+ "func-names": ["error"],
+ "func-style": ["error", "declaration", { "allowArrowFunctions": true }],
+ "key-spacing": ["error"],
+ "keyword-spacing": ["error"],
+ "no-trailing-spaces": ["error"],
+ "semi": ["error"],
+ "space-before-blocks": ["error"],
+ "space-before-function-paren": ["error", { "anonymous": "always",
+ "named": "never",
+ "asyncArrow": "always" }],
+ "switch-colon-spacing": ["error"],
+ }
+}
diff --git a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..94ac6f8
--- /dev/null
+++ b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,34 @@
+---
+name: Bug report
+about: Create a report to help us improve
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Client (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser: [e.g. chrome, safari]
+ - Browser version: [e.g. 22]
+
+**Server (please complete the following information):**
+ - noVNC version: [e.g. 1.0.0 or git commit id]
+ - VNC server: [e.g. QEMU, TigerVNC]
+ - WebSocket proxy: [e.g. websockify]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..066b2d9
--- /dev/null
+++ b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/systemvm/agent/noVNC/.gitignore b/systemvm/agent/noVNC/.gitignore
new file mode 100644
index 0000000..c178dba
--- /dev/null
+++ b/systemvm/agent/noVNC/.gitignore
@@ -0,0 +1,12 @@
+*.pyc
+*.o
+tests/data_*.js
+utils/rebind.so
+utils/websockify
+/node_modules
+/build
+/lib
+recordings
+*.swp
+*~
+noVNC-*.tgz
diff --git a/systemvm/agent/noVNC/.gitmodules b/systemvm/agent/noVNC/.gitmodules
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/systemvm/agent/noVNC/.gitmodules
diff --git a/systemvm/agent/noVNC/.travis.yml b/systemvm/agent/noVNC/.travis.yml
new file mode 100644
index 0000000..78b521a
--- /dev/null
+++ b/systemvm/agent/noVNC/.travis.yml
@@ -0,0 +1,58 @@
+language: node_js
+sudo: false
+cache:
+ directories:
+ - node_modules
+node_js:
+ - 6
+env:
+ matrix:
+ - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10'
+# FIXME Skip tests in Linux since Sauce Labs browser versions are ancient.
+# - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Linux'
+ - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='OS X 10.11'
+ - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10'
+# - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Linux'
+ - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='OS X 10.11'
+ - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10'
+ - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7'
+ - TEST_BROWSER_NAME=microsoftedge TEST_BROWSER_OS='Windows 10'
+ - TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.13'
+before_script: npm install -g karma-cli
+addons:
+ sauce_connect:
+ username: "directxman12"
+ jwt:
+ secure: "d3ekMYslpn6R4f0ajtRMt9SUFmNGDiItHpqaXC5T4KI0KMEsxgvEOfJot5PiFFJWg1DSpJZH6oaW2UxGZ3duJLZrXIEd/JePY8a6NtT35BNgiDPgcp+eu2Bu3rhrSNg7/HEsD1ma+JeUTnv18Ai5oMFfCCQJx2J6osIxyl/ZVxA="
+stages:
+- lint
+- test
+- name: deploy
+ if: tag is PRESENT
+jobs:
+ include:
+ - stage: lint
+ env:
+ addons:
+ before_script:
+ script: npm run lint
+ -
+ env:
+ addons:
+ before_script:
+ script: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate
+ - stage: deploy
+ env:
+ addons:
+ script: skip
+ before_script: skip
+ deploy:
+ provider: npm
+ email: ossman@cendio.se
+ api_key:
+ secure: "Qq2Mi9xQawO2zlAigzshzMu2QMHvu1IaN9l0ZIivE99wHJj7eS5f4miJ9wB+/mWRRgb3E8uj9ZRV24+Oc36drlBTU9sz+lHhH0uFMfAIseceK64wZV9sLAZm472fmPp2xdUeTCCqPaRy7g1XBqiJ0LyZvEFLsRijqcLjPBF+b8w="
+ on:
+ tags: true
+ repo: novnc/noVNC
+
+
diff --git a/systemvm/agent/noVNC/AUTHORS b/systemvm/agent/noVNC/AUTHORS
new file mode 100644
index 0000000..dec0e89
--- /dev/null
+++ b/systemvm/agent/noVNC/AUTHORS
@@ -0,0 +1,13 @@
+maintainers:
+- Joel Martin (@kanaka)
+- Solly Ross (@directxman12)
+- Samuel Mannehed for Cendio AB (@samhed)
+- Pierre Ossman for Cendio AB (@CendioOssman)
+maintainersEmeritus:
+- @astrand
+contributors:
+# There are a bunch of people that should be here.
+# If you want to be on this list, feel free send a PR
+# to add yourself.
+- jalf <git@jalf.dk>
+- NTT corp.
diff --git a/systemvm/agent/noVNC/LICENSE.txt b/systemvm/agent/noVNC/LICENSE.txt
new file mode 100644
index 0000000..20f3eb0
--- /dev/null
+++ b/systemvm/agent/noVNC/LICENSE.txt
@@ -0,0 +1,68 @@
+noVNC is Copyright (C) 2018 The noVNC Authors
+(./AUTHORS)
+
+The noVNC core library files are licensed under the MPL 2.0 (Mozilla
+Public License 2.0). The noVNC core library is composed of the
+Javascript code necessary for full noVNC operation. This includes (but
+is not limited to):
+
+ core/**/*.js
+ app/*.js
+ test/playback.js
+
+The HTML, CSS, font and images files that included with the noVNC
+source distibution (or repository) are not considered part of the
+noVNC core library and are licensed under more permissive licenses.
+The intent is to allow easy integration of noVNC into existing web
+sites and web applications.
+
+The HTML, CSS, font and image files are licensed as follows:
+
+ *.html : 2-Clause BSD license
+
+ app/styles/*.css : 2-Clause BSD license
+
+ app/styles/Orbitron* : SIL Open Font License 1.1
+ (Copyright 2009 Matt McInerney)
+
+ app/images/ : Creative Commons Attribution-ShareAlike
+ http://creativecommons.org/licenses/by-sa/3.0/
+
+Some portions of noVNC are copyright to their individual authors.
+Please refer to the individual source files and/or to the noVNC commit
+history: https://github.com/novnc/noVNC/commits/master
+
+The are several files and projects that have been incorporated into
+the noVNC core library. Here is a list of those files and the original
+licenses (all MPL 2.0 compatible):
+
+ core/base64.js : MPL 2.0
+
+ core/des.js : Various BSD style licenses
+
+ vendor/pako/ : MIT
+
+ vendor/browser-es-module-loader/src/ : MIT
+
+ vendor/browser-es-module-loader/dist/ : Various BSD style licenses
+
+ vendor/promise.js : MIT
+
+Any other files not mentioned above are typically marked with
+a copyright/license header at the top of the file. The default noVNC
+license is MPL-2.0.
+
+The following license texts are included:
+
+ docs/LICENSE.MPL-2.0
+ docs/LICENSE.OFL-1.1
+ docs/LICENSE.BSD-3-Clause (New BSD)
+ docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
+ vendor/pako/LICENSE (MIT)
+
+Or alternatively the license texts may be found here:
+
+ http://www.mozilla.org/MPL/2.0/
+ http://scripts.sil.org/OFL
+ http://en.wikipedia.org/wiki/BSD_licenses
+ https://opensource.org/licenses/MIT
diff --git a/systemvm/agent/noVNC/README.md b/systemvm/agent/noVNC/README.md
new file mode 100644
index 0000000..566b8e4
--- /dev/null
+++ b/systemvm/agent/noVNC/README.md
@@ -0,0 +1,152 @@
+## noVNC: HTML VNC Client Library and Application
+
+[](https://travis-ci.org/novnc/noVNC)
+
+### Description
+
+noVNC is both a HTML VNC client JavaScript library and an application built on
+top of that library. noVNC runs well in any modern browser including mobile
+browsers (iOS and Android).
+
+Many companies, projects and products have integrated noVNC including
+[OpenStack](http://www.openstack.org),
+[OpenNebula](http://opennebula.org/),
+[LibVNCServer](http://libvncserver.sourceforge.net), and
+[ThinLinc](https://cendio.com/thinlinc). See
+[the Projects and Companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC)
+for a more complete list with additional info and links.
+
+### Table of Contents
+
+- [News/help/contact](#newshelpcontact)
+- [Features](#features)
+- [Screenshots](#screenshots)
+- [Browser Requirements](#browser-requirements)
+- [Server Requirements](#server-requirements)
+- [Quick Start](#quick-start)
+- [Integration and Deployment](#integration-and-deployment)
+- [Authors/Contributors](#authorscontributors)
+
+### News/help/contact
+
+The project website is found at [novnc.com](http://novnc.com).
+Notable commits, announcements and news are posted to
+[@noVNC](http://www.twitter.com/noVNC).
+
+If you are a noVNC developer/integrator/user (or want to be) please join the
+[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
+
+Bugs and feature requests can be submitted via
+[github issues](https://github.com/novnc/noVNC/issues). If you have questions
+about using noVNC then please first use the
+[discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
+We also have a [wiki](https://github.com/novnc/noVNC/wiki/) with lots of
+helpful information.
+
+If you are looking for a place to start contributing to noVNC, a good place to
+start would be the issues that are marked as
+["patchwelcome"](https://github.com/novnc/noVNC/issues?labels=patchwelcome).
+Please check our
+[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) though.
+
+If you want to show appreciation for noVNC you could donate to a great non-
+profits such as:
+[Compassion International](http://www.compassion.com/),
+[SIL](http://www.sil.org),
+[Habitat for Humanity](http://www.habitat.org),
+[Electronic Frontier Foundation](https://www.eff.org/),
+[Against Malaria Foundation](http://www.againstmalaria.com/),
+[Nothing But Nets](http://www.nothingbutnets.net/), etc.
+Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
+
+
+### Features
+
+* Supports all modern browsers including mobile (iOS, Android)
+* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
+* Supports scaling, clipping and resizing the desktop
+* Local cursor rendering
+* Clipboard copy/paste
+* Translations
+* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
+ [the license document](LICENSE.txt) for details
+
+### Screenshots
+
+Running in Firefox before and after connecting:
+
+<img src="http://novnc.com/img/noVNC-1-login.png" width=400>
+<img src="http://novnc.com/img/noVNC-3-connected.png" width=400>
+
+See more screenshots
+[here](http://novnc.com/screenshots.html).
+
+
+### Browser Requirements
+
+noVNC uses many modern web technologies so a formal requirement list is
+not available. However these are the minimum versions we are currently
+aware of:
+
+* Chrome 49, Firefox 44, Safari 10, Opera 36, IE 11, Edge 12
+
+
+### Server Requirements
+
+noVNC follows the standard VNC protocol, but unlike other VNC clients it does
+require WebSockets support. Many servers include support (e.g.
+[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
+[QEMU](http://www.qemu.org/), and
+[MobileVNC](http://www.smartlab.at/mobilevnc/)), but for the others you need to
+use a WebSockets to TCP socket proxy. noVNC has a sister project
+[websockify](https://github.com/novnc/websockify) that provides a simple such
+proxy.
+
+
+### Quick Start
+
+* Use the launch script to automatically download and start websockify, which
+ includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
+ used to specify the location of a running VNC server:
+
+ `./utils/launch.sh --vnc localhost:5901`
+
+* Point your browser to the cut-and-paste URL that is output by the launch
+ script. Hit the Connect button, enter a password if the VNC server has one
+ configured, and enjoy!
+
+
+### Integration and Deployment
+
+Please see our other documents for how to integrate noVNC in your own software,
+or deploying the noVNC application in production environments:
+
+* [Embedding](docs/EMBEDDING.md) - For the noVNC application
+* [Library](docs/LIBRARY.md) - For the noVNC JavaScript library
+
+
+### Authors/Contributors
+
+See [AUTHORS](AUTHORS) for a (full-ish) list of authors. If you're not on
+that list and you think you should be, feel free to send a PR to fix that.
+
+* Core team:
+ * [Joel Martin](https://github.com/kanaka)
+ * [Samuel Mannehed](https://github.com/samhed) (Cendio)
+ * [Peter Åstrand](https://github.com/astrand) (Cendio)
+ * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
+ * [Pierre Ossman](https://github.com/CendioOssman) (Cendio)
+
+* Notable contributions:
+ * UI and Icons : Pierre Ossman, Chris Gordon
+ * Original Logo : Michael Sersen
+ * tight encoding : Michael Tinglof (Mercuri.ca)
+
+* Included libraries:
+ * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
+ * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
+ * Pako : Vitaly Puzrin (https://github.com/nodeca/pako)
+
+Do you want to be on this list? Check out our
+[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) and
+start hacking!
diff --git a/systemvm/agent/noVNC/VERSION b/systemvm/agent/noVNC/VERSION
new file mode 100644
index 0000000..9084fa2
--- /dev/null
+++ b/systemvm/agent/noVNC/VERSION
@@ -0,0 +1 @@
+1.1.0
diff --git a/systemvm/agent/noVNC/app/error-handler.js b/systemvm/agent/noVNC/app/error-handler.js
new file mode 100644
index 0000000..8e29416
--- /dev/null
+++ b/systemvm/agent/noVNC/app/error-handler.js
@@ -0,0 +1,58 @@
+// NB: this should *not* be included as a module until we have
+// native support in the browsers, so that our error handler
+// can catch script-loading errors.
+
+// No ES6 can be used in this file since it's used for the translation
+/* eslint-disable prefer-arrow-callback */
+
+(function _scope() {
+ "use strict";
+
+ // Fallback for all uncought errors
+ function handleError(event, err) {
+ try {
+ const msg = document.getElementById('noVNC_fallback_errormsg');
+
+ // Only show the initial error
+ if (msg.hasChildNodes()) {
+ return false;
+ }
+
+ let div = document.createElement("div");
+ div.classList.add('noVNC_message');
+ div.appendChild(document.createTextNode(event.message));
+ msg.appendChild(div);
+
+ if (event.filename) {
+ div = document.createElement("div");
+ div.className = 'noVNC_location';
+ let text = event.filename;
+ if (event.lineno !== undefined) {
+ text += ":" + event.lineno;
+ if (event.colno !== undefined) {
+ text += ":" + event.colno;
+ }
+ }
+ div.appendChild(document.createTextNode(text));
+ msg.appendChild(div);
+ }
+
+ if (err && err.stack) {
+ div = document.createElement("div");
+ div.className = 'noVNC_stack';
+ div.appendChild(document.createTextNode(err.stack));
+ msg.appendChild(div);
+ }
+
+ document.getElementById('noVNC_fallback_error')
+ .classList.add("noVNC_open");
+ } catch (exc) {
+ document.write("noVNC encountered an error.");
+ }
+ // Don't return true since this would prevent the error
+ // from being printed to the browser console.
+ return false;
+ }
+ window.addEventListener('error', function onerror(evt) { handleError(evt, evt.error); });
+ window.addEventListener('unhandledrejection', function onreject(evt) { handleError(evt.reason, evt.reason); });
+})();
diff --git a/systemvm/agent/noVNC/app/images/alt.svg b/systemvm/agent/noVNC/app/images/alt.svg
new file mode 100644
index 0000000..e5bb461
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/alt.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="alt.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="18.205425"
+ inkscape:cy="17.531398"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <g
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text5290">
+ <path
+ d="m 9.9560547,1042.3329 -2.9394531,0 -0.4638672,1.3281 -1.8896485,0 2.7001953,-7.29 2.241211,0 2.7001958,7.29 -1.889649,0 -0.4589843,-1.3281 z m -2.4707031,-1.3526 1.9970703,0 -0.9960938,-2.9003 -1.0009765,2.9003 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5340" />
+ <path
+ d="m 13.188477,1036.0634 1.748046,0 0,7.5976 -1.748046,0 0,-7.5976 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5342" />
+ <path
+ d="m 18.535156,1036.6395 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151367,0.5176 0.151368,0.1318 0.600586,0.1318 l 0.898438,0 0,1.25 -1.499024,0 q -1.035156,0 -1.469726,-0.4297 -0.429688,-0.4345 -0.429688,-1.4697 l 0,-2.3193 -0.86914,0 0,-1.25 0.86914,0 0,-1.5528 1.748047,0 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5344" />
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/clipboard.svg b/systemvm/agent/noVNC/app/images/clipboard.svg
new file mode 100644
index 0000000..79af275
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/clipboard.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="clipboard.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="15.366606"
+ inkscape:cy="16.42981"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 9,6 6,6 C 5.4459889,6 5,6.4459889 5,7 l 0,13 c 0,0.554011 0.4459889,1 1,1 l 13,0 c 0.554011,0 1,-0.445989 1,-1 L 20,7 C 20,6.4459889 19.554011,6 19,6 l -3,0"
+ transform="translate(0,1027.3622)"
+ id="rect6083"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cssssssssc" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect6085"
+ width="7"
+ height="4"
+ x="9"
+ y="1031.3622"
+ ry="1.00002" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+ d="m 8.5071212,1038.8622 7.9999998,0"
+ id="path6087"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+ d="m 8.5071212,1041.8622 3.9999998,0"
+ id="path6089"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+ d="m 8.5071212,1044.8622 5.9999998,0"
+ id="path6091"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/connect.svg b/systemvm/agent/noVNC/app/images/connect.svg
new file mode 100644
index 0000000..56cde41
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/connect.svg
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="connect.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="37.14834"
+ inkscape:cy="1.9525926"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <g
+ id="g5103"
+ transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,-729.15757,315.8823)">
+ <path
+ sodipodi:nodetypes="cssssc"
+ inkscape:connector-curvature="0"
+ id="rect5096"
+ d="m 11,1040.3622 -5,0 c -1.108,0 -2,-0.892 -2,-2 l 0,-4 c 0,-1.108 0.892,-2 2,-2 l 5,0"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 14,1032.3622 5,0 c 1.108,0 2,0.892 2,2 l 0,4 c 0,1.108 -0.892,2 -2,2 l -5,0"
+ id="path5099"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cssssc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5101"
+ d="m 9,1036.3622 7,0"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/ctrl.svg b/systemvm/agent/noVNC/app/images/ctrl.svg
new file mode 100644
index 0000000..856e939
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/ctrl.svg
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="ctrl.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="18.205425"
+ inkscape:cy="17.531398"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <g
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text5290">
+ <path
+ d="m 9.1210938,1043.1898 q -0.5175782,0.2686 -1.0791016,0.4053 -0.5615235,0.1367 -1.171875,0.1367 -1.8212891,0 -2.8857422,-1.0156 -1.0644531,-1.0205 -1.0644531,-2.7637 0,-1.748 1.0644531,-2.7637 1.0644531,-1.0205 2.8857422,-1.0205 0.6103515,0 1.171875,0.1368 0.5615234,0.1367 1.0791016,0.4052 l 0,1.5088 q -0.522461,-0.3564 -1.0302735,-0.5224 -0.5078125,-0.1661 -1.0693359,-0.1661 -1.0058594,0 -1.5820313,0.6446 -0.5761719,0.6445 -0.5761719,1.7773 0,1.1279 0.5761719,1.7725 0.5761719,0.6445 1.5820313,0.6445 0.5615234,0 1.0693359,-0.166 0.5078125,-0.166 1.0302735,-0.5225 l 0,1.5088 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5370" />
+ <path
+ d="m 12.514648,1036.5687 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151368,0.5176 0.151367,0.1318 0.600586,0.1318 l 0.898437,0 0,1.25 -1.499023,0 q -1.035157,0 -1.469727,-0.4297 -0.429687,-0.4345 -0.429687,-1.4697 l 0,-2.3193 -0.8691411,0 0,-1.25 0.8691411,0 0,-1.5528 1.748046,0 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5372" />
+ <path
+ d="m 19.453125,1039.6107 q -0.229492,-0.1074 -0.458984,-0.1562 -0.22461,-0.054 -0.454102,-0.054 -0.673828,0 -1.040039,0.4345 -0.361328,0.4297 -0.361328,1.2354 l 0,2.5195 -1.748047,0 0,-5.4687 1.748047,0 0,0.8984 q 0.336914,-0.5371 0.771484,-0.7813 0.439453,-0.249 1.049805,-0.249 0.08789,0 0.19043,0.01 0.102539,0 0.297851,0.029 l 0.0049,1.582 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5374" />
+ <path
+ d="m 20.332031,1035.9926 1.748047,0 0,7.5976 -1.748047,0 0,-7.5976 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5376" />
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/ctrlaltdel.svg b/systemvm/agent/noVNC/app/images/ctrlaltdel.svg
new file mode 100644
index 0000000..d7744ea
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/ctrlaltdel.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="ctrlaltdel.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="8"
+ inkscape:cx="11.135667"
+ inkscape:cy="16.407428"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5253"
+ width="5"
+ height="5.0000172"
+ x="16"
+ y="1031.3622"
+ ry="1.0000174" />
+ <rect
+ y="1043.3622"
+ x="4"
+ height="5.0000172"
+ width="5"
+ id="rect5255"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ ry="1.0000174" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5257"
+ width="5"
+ height="5.0000172"
+ x="13"
+ y="1043.3622"
+ ry="1.0000174" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/disconnect.svg b/systemvm/agent/noVNC/app/images/disconnect.svg
new file mode 100644
index 0000000..6be7d18
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/disconnect.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="disconnect.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="25.05707"
+ inkscape:cy="11.594858"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <g
+ id="g5171"
+ transform="translate(-24.062499,-6.15775e-4)">
+ <path
+ id="path5110"
+ transform="translate(0,1027.3622)"
+ d="m 39.744141,3.4960938 c -0.769923,0 -1.539607,0.2915468 -2.121094,0.8730468 l -2.566406,2.5664063 1.414062,1.4140625 2.566406,-2.5664063 c 0.403974,-0.404 1.010089,-0.404 1.414063,0 l 2.828125,2.828125 c 0.40398,0.4039 0.403907,1.0101621 0,1.4140629 l -2.566406,2.566406 1.414062,1.414062 2.566406,-2.566406 c 1.163041,-1.1629 1.162968,-3.0791874 0,-4.2421874 L 41.865234,4.3691406 C 41.283747,3.7876406 40.514063,3.4960937 39.744141,3.4960938 Z M 39.017578,9.015625 a 1.0001,1.0001 0 0 0 -0.6875,0.3027344 l -0.445312,0.4453125 1.414062,1.4140621 0.445313,-0.445312 A 1.0001,1.0001 0 0 0 39.017578,9.015625 Z m -6.363281,0.7070312 a 1.0001,1.0001 0 0 0 -0.6875,0.3027348 L 28.431641,13.5625 c -1.163042,1.163 -1.16297,3.079187 0,4.242188 l 2.828125,2.828124 c 1.162974,1.163101 3.079213,1.163101 4.242187,0 l 3.535156,-3.535156 a 1.0001,1.0001 0 1 0 -1.414062,-1.414062 l -3.535156,3.535156 c -0.403974,0.404 -1.010089,0.404 -1.414063,0 l -2.828125,-2.828125 c -0.403981,-0.404 -0.403908,-1.010162 0,-1.414063 l 3.535156,-3.537109 A 1.0001,1.0001 0 0 0 32.654297,9.7226562 Z m 3.109375,2.1621098 -2.382813,2.384765 a 1.0001,1.0001 0 1 0 1.414063,1.414063 l 2.382812,-2.384766 -1.414062,-1.414062 z"
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <rect
+ transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"
+ y="752.29541"
+ x="-712.31262"
+ height="18.000017"
+ width="3"
+ id="rect5116"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/drag.svg b/systemvm/agent/noVNC/app/images/drag.svg
new file mode 100644
index 0000000..139caf9
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/drag.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="drag.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.627417"
+ inkscape:cx="9.8789407"
+ inkscape:cy="9.5008608"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 7.039733,1049.3037 c -0.4309106,-0.1233 -0.7932634,-0.4631 -0.9705434,-0.9103 -0.04922,-0.1241 -0.057118,-0.2988 -0.071321,-1.5771 l -0.015972,-1.4375 -0.328125,-0.082 c -0.7668138,-0.1927 -1.1897046,-0.4275 -1.7031253,-0.9457 -0.4586773,-0.4629 -0.6804297,-0.8433 -0.867034,-1.4875 -0.067215,-0.232 -0.068001,-0.2642 -0.078682,-3.2188 -0.012078,-3.341 -0.020337,-3.2012 0.2099452,-3.5555 0.2246623,-0.3458 0.5798271,-0.5892 0.9667343,-0.6626 0.092506,-0.017 0.531898,-0.032 0.9764271,-0.032 l 0.8082347,0 1.157e-4,1.336 c 1.125e-4,1.2779 0.00281,1.3403 0.062214,1.4378 0.091785,0.1505 0.2357707,0.226 0.4314082,0.2261 0.285389,2e-4 0.454884,-0.1352 0.5058962,-0.4042 0.019355,-0.102 0.031616,-0.982 0.031616,-2.269 0,-1.9756 0.00357,-2.1138 0.059205,-2.2926 0.1645475,-0.5287 0.6307616,-0.9246 1.19078,-1.0113 0.8000572,-0.1238 1.5711277,0.4446 1.6860387,1.2429 0.01732,0.1203 0.03177,0.8248 0.03211,1.5657 6.19e-4,1.3449 7.22e-4,1.347 0.07093,1.4499 0.108355,0.1587 0.255268,0.2248 0.46917,0.2108 0.204069,-0.013 0.316116,-0.08 0.413642,-0.2453 0.06028,-0.1024 0.06307,-0.1778 0.07862,-2.1218 0.01462,-1.8283 0.02124,-2.0285 0.07121,-2.1549 0.260673,-0.659 0.934894,-1.0527 1.621129,-0.9465 0.640523,0.099 1.152269,0.6104 1.243187,1.2421 0.01827,0.1269 0.03175,0.9943 0.03211,2.0657 l 6.19e-4,1.8469 0.07031,0.103 c 0.108355,0.1587 0.255267,0.2248 0.46917,0.2108 0.204069,-0.013 0.316115,-0.08 0.413642,-0.2453 0.05951,-0.1011 0.06329,-0.1786 0.07907,-1.6218 0.01469,-1.3438 0.02277,-1.5314 0.07121,-1.6549 0.257975,-0.6576 0.934425,-1.0527 1.620676,-0.9465 0.640522,0.099 1.152269,0.6104 1.243186,1.2421 0.0186,0.1292 0.03179,1.0759 0.03222,2.3125 7.15e-4,2.0335 0.0025,2.0966 0.06283,2.1956 0.09178,0.1505 0.235771,0.226 0.431409,0.2261 0.285388,2e-4 0.454884,-0.1352 0.505897,-0.4042 0.01874,-0.099 0.03161,-0.8192 0.03161,-1.769 0,-1.4848 0.0043,-1.6163 0.0592,-1.7926 0.164548,-0.5287 0.630762,-0.9246 1.19078,-1.0113 0.800057,-0.1238 1.571128,0.4446 1.686039,1.2429 0.04318,0.2999 0.04372,9.1764 5.78e-4,9.4531 -0.04431,0.2841 -0.217814,0.6241 -0.420069,0.8232 -0.320102,0.315 -0.63307,0.4268 -1.194973,0.4268 l -0.35281,0 -2.51e-4,1.2734 c -1.25e-4,0.7046 -0.01439,1.3642 -0.03191,1.4766 -0.06665,0.4274 -0.372966,0.8704 -0.740031,1.0702 -0.349999,0.1905 0.01748,0.18 -6.242199,0.1776 -5.3622439,0 -5.7320152,-0.01 -5.9121592,-0.057 l 1.4e-5,0 z"
+ id="path4379"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/error.svg b/systemvm/agent/noVNC/app/images/error.svg
new file mode 100644
index 0000000..8356d3f
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/error.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="error.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="14.00357"
+ inkscape:cy="12.443398"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 7 3 C 4.7839905 3 3 4.7839905 3 7 L 3 18 C 3 20.21601 4.7839905 22 7 22 L 18 22 C 20.21601 22 22 20.21601 22 18 L 22 7 C 22 4.7839905 20.21601 3 18 3 L 7 3 z M 7.6992188 6 A 1.6916875 1.6924297 0 0 1 8.9121094 6.5117188 L 12.5 10.101562 L 16.087891 6.5117188 A 1.6916875 1.6924297 0 0 1 17.251953 6 A 1.6916875 1.6924297 0 0 1 18.480469 8.90625 L 14.892578 12.496094 L 18.480469 16.085938 A 1.6916875 1.6924297 0 1 1 16.087891 18.478516 L 12.5 14.888672 L 8.9121094 18.478516 A 1.6916875 1.6924297 0 1 1 6.5214844 16.085938 L 10.109375 12.496094 L 6.5214844 8.90625 A 1.6916875 1.6924297 0 0 1 7.6992188 6 z "
+ transform="translate(0,1027.3622)"
+ id="rect4135" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/esc.svg b/systemvm/agent/noVNC/app/images/esc.svg
new file mode 100644
index 0000000..830152b
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/esc.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="esc.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="18.205425"
+ inkscape:cy="17.531398"
+ inkscape:document-units="px"
+ inkscape:current-layer="text5290"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <g
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text5290">
+ <path
+ d="m 3.9331055,1036.1464 5.0732422,0 0,1.4209 -3.1933594,0 0,1.3574 3.0029297,0 0,1.4209 -3.0029297,0 0,1.6699 3.3007812,0 0,1.4209 -5.180664,0 0,-7.29 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5314" />
+ <path
+ d="m 14.963379,1038.1385 0,1.3282 q -0.561524,-0.2344 -1.083984,-0.3516 -0.522461,-0.1172 -0.986329,-0.1172 -0.498046,0 -0.742187,0.127 -0.239258,0.122 -0.239258,0.3808 0,0.21 0.180664,0.3223 0.185547,0.1123 0.65918,0.166 l 0.307617,0.044 q 1.342773,0.1709 1.806641,0.5615 0.463867,0.3906 0.463867,1.2256 0,0.874 -0.644531,1.3134 -0.644532,0.4395 -1.923829,0.4395 -0.541992,0 -1.123046,-0.088 -0.576172,-0.083 -1.186524,-0.2539 l 0,-1.3281 q 0.522461,0.2539 1.069336,0.3808 0.551758,0.127 1.118164,0.127 0.512695,0 0.771485,-0.1416 0.258789,-0.1416 0.258789,-0.4199 0,-0.2344 -0.180664,-0.3467 -0.175782,-0.1172 -0.708008,-0.1807 l -0.307617,-0.039 q -1.166993,-0.1465 -1.635743,-0.542 -0.46875,-0.3955 -0.46875,-1.2012 0,-0.8691 0.595703,-1.2891 0.595704,-0.4199 1.826172,-0.4199 0.483399,0 1.015625,0.073 0.532227,0.073 1.157227,0.2294 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5316" />
+ <path
+ d="m 21.066895,1038.1385 0,1.4258 q -0.356446,-0.2441 -0.717774,-0.3613 -0.356445,-0.1172 -0.742187,-0.1172 -0.732422,0 -1.142579,0.4297 -0.405273,0.4248 -0.405273,1.1914 0,0.7666 0.405273,1.1963 0.410157,0.4248 1.142579,0.4248 0.410156,0 0.776367,-0.1221 0.371094,-0.122 0.683594,-0.3613 l 0,1.4307 q -0.410157,0.1513 -0.834961,0.2246 -0.419922,0.078 -0.844727,0.078 -1.479492,0 -2.314453,-0.7568 -0.834961,-0.7618 -0.834961,-2.1143 0,-1.3525 0.834961,-2.1094 0.834961,-0.7617 2.314453,-0.7617 0.429688,0 0.844727,0.078 0.419921,0.073 0.834961,0.2246 z"
+ style="font-size:10px;fill:#ffffff;fill-opacity:1"
+ id="path5318" />
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/expander.svg b/systemvm/agent/noVNC/app/images/expander.svg
new file mode 100644
index 0000000..e163535
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/expander.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="9"
+ height="10"
+ viewBox="0 0 9 10"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="expander.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="45.254834"
+ inkscape:cx="9.8737281"
+ inkscape:cy="6.4583132"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ inkscape:snap-object-midpoints="false"
+ inkscape:object-nodes="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1042.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="M 2.0800781,1042.3633 A 2.0002,2.0002 0 0 0 0,1044.3613 l 0,6 a 2.0002,2.0002 0 0 0 3.0292969,1.7168 l 5,-3 a 2.0002,2.0002 0 0 0 0,-3.4316 l -5,-3 a 2.0002,2.0002 0 0 0 -0.9492188,-0.2832 z"
+ id="path4138"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/fullscreen.svg b/systemvm/agent/noVNC/app/images/fullscreen.svg
new file mode 100644
index 0000000..29bd05d
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/fullscreen.svg
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="fullscreen.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="16.400723"
+ inkscape:cy="15.083758"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5006"
+ width="17"
+ height="17.000017"
+ x="4"
+ y="1031.3622"
+ ry="3.0000174" />
+ <path
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ d="m 7.5,1044.8622 4,0 -1.5,-1.5 1.5,-1.5 -1,-1 -1.5,1.5 -1.5,-1.5 0,4 z"
+ id="path5017"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5025"
+ d="m 17.5,1034.8622 -4,0 1.5,1.5 -1.5,1.5 1,1 1.5,-1.5 1.5,1.5 0,-4 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/handle.svg b/systemvm/agent/noVNC/app/images/handle.svg
new file mode 100644
index 0000000..4a7a126
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/handle.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="5"
+ height="6"
+ viewBox="0 0 5 6"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="handle.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="32"
+ inkscape:cx="1.3551778"
+ inkscape:cy="8.7800329"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1046.3622)">
+ <path
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 4.0000803,1049.3622 -3,-2 0,4 z"
+ id="path4247"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/handle_bg.svg b/systemvm/agent/noVNC/app/images/handle_bg.svg
new file mode 100644
index 0000000..7579c42
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/handle_bg.svg
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="15"
+ height="50"
+ viewBox="0 0 15 50"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="handle_bg.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="-10.001409"
+ inkscape:cy="24.512566"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1002.3622)">
+ <rect
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4249"
+ width="1"
+ height="1.0000174"
+ x="9.5"
+ y="1008.8622"
+ ry="1.7382812e-05" />
+ <rect
+ ry="1.7382812e-05"
+ y="1013.8622"
+ x="9.5"
+ height="1.0000174"
+ width="1"
+ id="rect4255"
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ ry="1.7382812e-05"
+ y="1008.8622"
+ x="4.5"
+ height="1.0000174"
+ width="1"
+ id="rect4261"
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4263"
+ width="1"
+ height="1.0000174"
+ x="4.5"
+ y="1013.8622"
+ ry="1.7382812e-05" />
+ <rect
+ ry="1.7382812e-05"
+ y="1039.8622"
+ x="9.5"
+ height="1.0000174"
+ width="1"
+ id="rect4265"
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4267"
+ width="1"
+ height="1.0000174"
+ x="9.5"
+ y="1044.8622"
+ ry="1.7382812e-05" />
+ <rect
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4269"
+ width="1"
+ height="1.0000174"
+ x="4.5"
+ y="1039.8622"
+ ry="1.7382812e-05" />
+ <rect
+ ry="1.7382812e-05"
+ y="1044.8622"
+ x="4.5"
+ height="1.0000174"
+ width="1"
+ id="rect4271"
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4273"
+ width="1"
+ height="1.0000174"
+ x="9.5"
+ y="1018.8622"
+ ry="1.7382812e-05" />
+ <rect
+ ry="1.7382812e-05"
+ y="1018.8622"
+ x="4.5"
+ height="1.0000174"
+ width="1"
+ id="rect4275"
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4277"
+ width="1"
+ height="1.0000174"
+ x="9.5"
+ y="1034.8622"
+ ry="1.7382812e-05" />
+ <rect
+ ry="1.7382812e-05"
+ y="1034.8622"
+ x="4.5"
+ height="1.0000174"
+ width="1"
+ id="rect4279"
+ style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/icons/Makefile b/systemvm/agent/noVNC/app/images/icons/Makefile
new file mode 100644
index 0000000..be564b4
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/Makefile
@@ -0,0 +1,42 @@
+ICONS := \
+ novnc-16x16.png \
+ novnc-24x24.png \
+ novnc-32x32.png \
+ novnc-48x48.png \
+ novnc-64x64.png
+
+ANDROID_LAUNCHER := \
+ novnc-48x48.png \
+ novnc-72x72.png \
+ novnc-96x96.png \
+ novnc-144x144.png \
+ novnc-192x192.png
+
+IPHONE_LAUNCHER := \
+ novnc-60x60.png \
+ novnc-120x120.png
+
+IPAD_LAUNCHER := \
+ novnc-76x76.png \
+ novnc-152x152.png
+
+ALL_ICONS := $(ICONS) $(ANDROID_LAUNCHER) $(IPHONE_LAUNCHER) $(IPAD_LAUNCHER)
+
+all: $(ALL_ICONS)
+
+novnc-16x16.png: novnc-icon-sm.svg
+ convert -density 90 \
+ -background transparent "$<" "$@"
+novnc-24x24.png: novnc-icon-sm.svg
+ convert -density 135 \
+ -background transparent "$<" "$@"
+novnc-32x32.png: novnc-icon-sm.svg
+ convert -density 180 \
+ -background transparent "$<" "$@"
+
+novnc-%.png: novnc-icon.svg
+ convert -density $$[`echo $* | cut -d x -f 1` * 90 / 48] \
+ -background transparent "$<" "$@"
+
+clean:
+ rm -f *.png
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-120x120.png b/systemvm/agent/noVNC/app/images/icons/novnc-120x120.png
new file mode 100644
index 0000000..40823ef
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-120x120.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-144x144.png b/systemvm/agent/noVNC/app/images/icons/novnc-144x144.png
new file mode 100644
index 0000000..eee71f1
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-144x144.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-152x152.png b/systemvm/agent/noVNC/app/images/icons/novnc-152x152.png
new file mode 100644
index 0000000..0694b2d
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-152x152.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-16x16.png b/systemvm/agent/noVNC/app/images/icons/novnc-16x16.png
new file mode 100644
index 0000000..42108f4
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-16x16.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-192x192.png b/systemvm/agent/noVNC/app/images/icons/novnc-192x192.png
new file mode 100644
index 0000000..ef9201f
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-192x192.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-24x24.png b/systemvm/agent/noVNC/app/images/icons/novnc-24x24.png
new file mode 100644
index 0000000..1106135
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-24x24.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-32x32.png b/systemvm/agent/noVNC/app/images/icons/novnc-32x32.png
new file mode 100644
index 0000000..ff00dc3
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-32x32.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-48x48.png b/systemvm/agent/noVNC/app/images/icons/novnc-48x48.png
new file mode 100644
index 0000000..f24cd6c
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-48x48.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-60x60.png b/systemvm/agent/noVNC/app/images/icons/novnc-60x60.png
new file mode 100644
index 0000000..06b0d60
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-60x60.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-64x64.png b/systemvm/agent/noVNC/app/images/icons/novnc-64x64.png
new file mode 100644
index 0000000..6d0fb34
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-64x64.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-72x72.png b/systemvm/agent/noVNC/app/images/icons/novnc-72x72.png
new file mode 100644
index 0000000..23163a2
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-72x72.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-76x76.png b/systemvm/agent/noVNC/app/images/icons/novnc-76x76.png
new file mode 100644
index 0000000..aef61c4
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-76x76.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-96x96.png b/systemvm/agent/noVNC/app/images/icons/novnc-96x96.png
new file mode 100644
index 0000000..1a77c53
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-96x96.png
Binary files differ
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-icon-sm.svg b/systemvm/agent/noVNC/app/images/icons/novnc-icon-sm.svg
new file mode 100644
index 0000000..aa1c6f1
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-icon-sm.svg
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ viewBox="0 0 16 16"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="novnc-icon-sm.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="45.254834"
+ inkscape:cx="9.722703"
+ inkscape:cy="5.5311896"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4169" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1036.3621)">
+ <rect
+ style="opacity:1;fill:#494949;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4167"
+ width="16"
+ height="15.999992"
+ x="0"
+ y="1036.3622"
+ ry="2.6666584" />
+ <path
+ style="opacity:1;fill:#313131;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 2.6666667,1036.3621 C 1.1893373,1036.3621 0,1037.5515 0,1039.0288 l 0,10.6666 c 0,1.4774 1.1893373,2.6667 2.6666667,2.6667 l 4,0 C 11.837333,1052.3621 16,1046.7128 16,1039.6955 l 0,-0.6667 c 0,-1.4773 -1.189337,-2.6667 -2.666667,-2.6667 l -10.6666663,0 z"
+ id="rect4173"
+ inkscape:connector-curvature="0" />
+ <g
+ id="g4381">
+ <g
+ transform="translate(0.25,0.25)"
+ style="fill:#000000;fill-opacity:1"
+ id="g4365">
+ <g
+ style="fill:#000000;fill-opacity:1"
+ id="g4367">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4369"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 4.3289754,1039.3621 c 0.1846149,0 0.3419956,0.071 0.4716623,0.2121 C 4.933546,1039.7121 5,1039.8793 5,1040.0759 l 0,3.2862 -1,0 0,-2.964 c 0,-0.024 -0.011592,-0.036 -0.034038,-0.036 l -1.931924,0 C 2.011349,1040.3621 2,1040.3741 2,1040.3981 l 0,2.964 -1,0 0,-4 z"
+ sodipodi:nodetypes="scsccsssscccs" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4371"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 6.6710244,1039.3621 2.6579513,0 c 0.184775,0 0.3419957,0.071 0.471662,0.2121 C 9.933546,1039.7121 10,1039.8793 10,1040.0759 l 0,2.5724 c 0,0.1966 -0.066454,0.3655 -0.1993623,0.5069 -0.1296663,0.1379 -0.286887,0.2069 -0.471662,0.2069 l -2.6579513,0 c -0.184775,0 -0.3436164,-0.069 -0.4765247,-0.2069 C 6.0648334,1043.0138 6,1042.8449 6,1042.6483 l 0,-2.5724 c 0,-0.1966 0.064833,-0.3638 0.1944997,-0.5017 0.1329083,-0.1414 0.2917497,-0.2121 0.4765247,-0.2121 z m 2.2949386,1 -1.931926,0 C 7.011344,1040.3621 7,1040.3741 7,1040.3981 l 0,1.928 c 0,0.024 0.011347,0.036 0.034037,0.036 l 1.931926,0 c 0.02269,0 0.034037,-0.012 0.034037,-0.036 l 0,-1.928 c 0,-0.024 -0.011347,-0.036 -0.034037,-0.036 z"
+ sodipodi:nodetypes="sscsscsscsscssssssssss" />
+ </g>
+ <g
+ style="fill:#000000;fill-opacity:1"
+ id="g4373">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4375"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 3,1047.1121 1,-2.75 1,0 -1.5,4 -1,0 -1.5,-4 1,0 z"
+ sodipodi:nodetypes="cccccccc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4377"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 9,1046.8621 0,-2.5 1,0 0,4 -1,0 -2,-2.5 0,2.5 -1,0 0,-4 1,0 z"
+ sodipodi:nodetypes="ccccccccccc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4379"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 15,1045.3621 -2.96596,0 c -0.02269,0 -0.03404,0.012 -0.03404,0.036 l 0,1.928 c 0,0.024 0.01135,0.036 0.03404,0.036 l 2.96596,0 0,1 -3.324113,0 c -0.188017,0 -0.348479,-0.068 -0.481388,-0.2037 C 11.064833,1048.0192 11,1047.8511 11,1047.6542 l 0,-2.5842 c 0,-0.1969 0.06483,-0.3633 0.194499,-0.4991 0.132909,-0.1392 0.293371,-0.2088 0.481388,-0.2088 l 3.324113,0 z"
+ sodipodi:nodetypes="cssssccscsscscc" />
+ </g>
+ </g>
+ <g
+ id="g4356">
+ <g
+ id="g4347">
+ <path
+ sodipodi:nodetypes="scsccsssscccs"
+ d="m 4.3289754,1039.3621 c 0.1846149,0 0.3419956,0.071 0.4716623,0.2121 C 4.933546,1039.7121 5,1039.8793 5,1040.0759 l 0,3.2862 -1,0 0,-2.964 c 0,-0.024 -0.011592,-0.036 -0.034038,-0.036 l -1.931924,0 c -0.022689,0 -0.034038,0.012 -0.034038,0.036 l 0,2.964 -1,0 0,-4 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4143"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="sscsscsscsscssssssssss"
+ d="m 6.6710244,1039.3621 2.6579513,0 c 0.184775,0 0.3419957,0.071 0.471662,0.2121 C 9.933546,1039.7121 10,1039.8793 10,1040.0759 l 0,2.5724 c 0,0.1966 -0.066454,0.3655 -0.1993623,0.5069 -0.1296663,0.1379 -0.286887,0.2069 -0.471662,0.2069 l -2.6579513,0 c -0.184775,0 -0.3436164,-0.069 -0.4765247,-0.2069 C 6.0648334,1043.0138 6,1042.8449 6,1042.6483 l 0,-2.5724 c 0,-0.1966 0.064833,-0.3638 0.1944997,-0.5017 0.1329083,-0.1414 0.2917497,-0.2121 0.4765247,-0.2121 z m 2.2949386,1 -1.931926,0 C 7.011344,1040.3621 7,1040.3741 7,1040.3981 l 0,1.928 c 0,0.024 0.011347,0.036 0.034037,0.036 l 1.931926,0 c 0.02269,0 0.034037,-0.012 0.034037,-0.036 l 0,-1.928 c 0,-0.024 -0.011347,-0.036 -0.034037,-0.036 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4145"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g4351">
+ <path
+ sodipodi:nodetypes="cccccccc"
+ d="m 3,1047.1121 1,-2.75 1,0 -1.5,4 -1,0 -1.5,-4 1,0 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4147"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="ccccccccccc"
+ d="m 9,1046.8621 0,-2.5 1,0 0,4 -1,0 -2,-2.5 0,2.5 -1,0 0,-4 1,0 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4149"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="cssssccscsscscc"
+ d="m 15,1045.3621 -2.96596,0 c -0.02269,0 -0.03404,0.012 -0.03404,0.036 l 0,1.928 c 0,0.024 0.01135,0.036 0.03404,0.036 l 2.96596,0 0,1 -3.324113,0 c -0.188017,0 -0.348479,-0.068 -0.481388,-0.2037 C 11.064833,1048.0192 11,1047.8511 11,1047.6542 l 0,-2.5842 c 0,-0.1969 0.06483,-0.3633 0.194499,-0.4991 0.132909,-0.1392 0.293371,-0.2088 0.481388,-0.2088 l 3.324113,0 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4151"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/icons/novnc-icon.svg b/systemvm/agent/noVNC/app/images/icons/novnc-icon.svg
new file mode 100644
index 0000000..1efff91
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/icons/novnc-icon.svg
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ viewBox="0 0 48 48.000001"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="novnc-icon.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="27.187245"
+ inkscape:cy="17.700974"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4169" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1004.3621)">
+ <rect
+ style="opacity:1;fill:#494949;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4167"
+ width="48"
+ height="48"
+ x="0"
+ y="1004.3621"
+ ry="7.9999785" />
+ <path
+ style="opacity:1;fill:#313131;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 8,1004.3621 c -4.4319881,0 -8,3.568 -8,8 l 0,32 c 0,4.432 3.5680119,8 8,8 l 12,0 c 15.512,0 28,-16.948 28,-38 l 0,-2 c 0,-4.432 -3.568012,-8 -8,-8 l -32,0 z"
+ id="rect4173"
+ inkscape:connector-curvature="0" />
+ <g
+ id="g4300"
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ transform="translate(0.5,0.5)">
+ <g
+ id="g4302"
+ style="fill:#000000;fill-opacity:1;stroke:none">
+ <path
+ sodipodi:nodetypes="scsccsssscccs"
+ d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 l 0,6.8586 -2,0 0,-6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 l -4.7957745,0 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 l 0,6.8914 -2,0 0,-9 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4304"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="sscsscsscsscssssssssss"
+ d="m 17.013073,1016.3621 4.973854,0 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 l 0,4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 l -4.973854,0 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 l 0,-4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 -4.795776,0 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 l 0,4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 l 4.795776,0 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 l 0,-4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4306"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g4308"
+ style="fill:#000000;fill-opacity:1;stroke:none">
+ <path
+ sodipodi:nodetypes="cccccccc"
+ d="m 12,1036.9177 4.768114,-8.5556 2.231886,0 -6,11 -2,0 -6,-11 2.2318854,0 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4310"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="ccccccccccc"
+ d="m 29,1036.3621 0,-8 2,0 0,11 -2,0 -7,-8 0,8 -2,0 0,-11 2,0 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4312"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="cssssccscsscscc"
+ d="m 43,1030.3621 -8.897887,0 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 l 0,6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 l 8.897887,0 0,2 -8.972339,0 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 l 0,-6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 l 8.972339,0 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4314"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <g
+ id="g4291"
+ style="stroke:none">
+ <g
+ id="g4282"
+ style="stroke:none">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4143"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 l 0,6.8586 -2,0 0,-6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 l -4.7957745,0 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 l 0,6.8914 -2,0 0,-9 z"
+ sodipodi:nodetypes="scsccsssscccs" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4145"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 17.013073,1016.3621 4.973854,0 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 l 0,4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 l -4.973854,0 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 l 0,-4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 -4.795776,0 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 l 0,4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 l 4.795776,0 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 l 0,-4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
+ sodipodi:nodetypes="sscsscsscsscssssssssss" />
+ </g>
+ <g
+ id="g4286"
+ style="stroke:none">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4147"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 12,1036.9177 4.768114,-8.5556 2.231886,0 -6,11 -2,0 -6,-11 2.2318854,0 z"
+ sodipodi:nodetypes="cccccccc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4149"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 29,1036.3621 0,-8 2,0 0,11 -2,0 -7,-8 0,8 -2,0 0,-11 2,0 z"
+ sodipodi:nodetypes="ccccccccccc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4151"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 43,1030.3621 -8.897887,0 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 l 0,6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 l 8.897887,0 0,2 -8.972339,0 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 l 0,-6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 l 8.972339,0 z"
+ sodipodi:nodetypes="cssssccscsscscc" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/info.svg b/systemvm/agent/noVNC/app/images/info.svg
new file mode 100644
index 0000000..557b772
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/info.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="info.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="15.720838"
+ inkscape:cy="8.9111233"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 12.5 3 A 9.5 9.4999914 0 0 0 3 12.5 A 9.5 9.4999914 0 0 0 12.5 22 A 9.5 9.4999914 0 0 0 22 12.5 A 9.5 9.4999914 0 0 0 12.5 3 z M 12.5 5 A 1.5 1.5000087 0 0 1 14 6.5 A 1.5 1.5000087 0 0 1 12.5 8 A 1.5 1.5000087 0 0 1 11 6.5 A 1.5 1.5000087 0 0 1 12.5 5 z M 10.521484 8.9785156 L 12.521484 8.9785156 A 1.50015 1.50015 0 0 1 14.021484 10.478516 L 14.021484 15.972656 A 1.50015 1.50015 0 0 1 14.498047 18.894531 C 14.498047 18.894531 13.74301 19.228309 12.789062 18.912109 C 12.312092 18.754109 11.776235 18.366625 11.458984 17.828125 C 11.141734 17.289525 11.021484 16.668469 11.021484 15.980469 L 11.021484 11.980469 L 10.521484 11.980469 A 1.50015 1.50015 0 1 1 10.521484 8.9804688 L 10.521484 8.9785156 z "
+ transform="translate(0,1027.3622)"
+ id="path4136" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/keyboard.svg b/systemvm/agent/noVNC/app/images/keyboard.svg
new file mode 100644
index 0000000..137b350
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/keyboard.svg
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="keyboard.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/keyboard.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#717171"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="31.285341"
+ inkscape:cy="8.8028469"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-smooth-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="M 7,3 C 4.8012876,3 3,4.8013 3,7 3,11.166667 3,15.333333 3,19.5 3,20.8764 4.1236413,22 5.5,22 l 14,0 C 20.876358,22 22,20.8764 22,19.5 22,15.333333 22,11.166667 22,7 22,4.8013 20.198712,3 18,3 Z m 0,2 11,0 c 1.125307,0 2,0.8747 2,2 L 20,12 5,12 5,7 C 5,5.8747 5.8746931,5 7,5 Z M 6.5,14 C 6.777,14 7,14.223 7,14.5 7,14.777 6.777,15 6.5,15 6.223,15 6,14.777 6,14.5 6,14.223 6.223,14 6.5,14 Z m 2,0 C 8.777,14 9,14.223 9,14.5 9,14.777 8.777,15 8.5,15 8.223,15 8,14.777 8,14.5 8,14.223 8.223,14 8.5,14 Z m 2,0 C 10.777,14 11,14.223 11,14.5 11,14.777 10.777,15 10.5,15 10.223,15 10,14.777 10,14.5 10,14.223 10.223,14 10.5,14 Z m 2,0 C 12.777,14 13,14.223 13,14.5 13,14.777 12.777,15 12.5,15 12.223,15 12,14.777 12,14.5 12,14.223 12.223,14 12.5,14 Z m 2,0 C 14.777,14 15,14.223 15,14.5 15,14.777 14.777,15 14.5,15 14.223,15 14,14.777 14,14.5 14,14.223 14.223,14 14.5,14 Z m 2,0 C 16.777,14 17,14.223 17,14.5 17,14.777 16.777,15 16.5,15 16.223,15 16,14.777 16,14.5 16,14.223 16.223,14 16.5,14 Z m 2,0 C 18.777,14 19,14.223 19,14.5 19,14.777 18.777,15 18.5,15 18.223,15 18,14.777 18,14.5 18,14.223 18.223,14 18.5,14 Z m -13,2 C 5.777,16 6,16.223 6,16.5 6,16.777 5.777,17 5.5,17 5.223,17 5,16.777 5,16.5 5,16.223 5.223,16 5.5,16 Z m 2,0 C 7.777,16 8,16.223 8,16.5 8,16.777 7.777,17 7.5,17 7.223,17 7,16.777 7,16.5 7,16.223 7.223,16 7.5,16 Z m 2,0 C 9.777,16 10,16.223 10,16.5 10,16.777 9.777,17 9.5,17 9.223,17 9,16.777 9,16.5 9,16.223 9.223,16 9.5,16 Z m 2,0 C 11.777,16 12,16.223 12,16.5 12,16.777 11.777,17 11.5,17 11.223,17 11,16.777 11,16.5 11,16.223 11.223,16 11.5,16 Z m 2,0 C 13.777,16 14,16.223 14,16.5 14,16.777 13.777,17 13.5,17 13.223,17 13,16.777 13,16.5 13,16.223 13.223,16 13.5,16 Z m 2,0 C 15.777,16 16,16.223 16,16.5 16,16.777 15.777,17 15.5,17 15.223,17 15,16.777 15,16.5 15,16.223 15.223,16 15.5,16 Z m 2,0 C 17.777,16 18,16.223 18,16.5 18,16.777 17.777,17 17.5,17 17.223,17 17,16.777 17,16.5 17,16.223 17.223,16 17.5,16 Z m 2,0 C 19.777,16 20,16.223 20,16.5 20,16.777 19.777,17 19.5,17 19.223,17 19,16.777 19,16.5 19,16.223 19.223,16 19.5,16 Z M 6,18 c 0.554,0 1,0.446 1,1 0,0.554 -0.446,1 -1,1 -0.554,0 -1,-0.446 -1,-1 0,-0.554 0.446,-1 1,-1 z m 2.8261719,0 7.3476561,0 C 16.631643,18 17,18.368372 17,18.826172 l 0,0.347656 C 17,19.631628 16.631643,20 16.173828,20 L 8.8261719,20 C 8.3683573,20 8,19.631628 8,19.173828 L 8,18.826172 C 8,18.368372 8.3683573,18 8.8261719,18 Z m 10.1113281,0 0.125,0 C 19.581551,18 20,18.4184 20,18.9375 l 0,0.125 C 20,19.5816 19.581551,20 19.0625,20 l -0.125,0 C 18.418449,20 18,19.5816 18,19.0625 l 0,-0.125 C 18,18.4184 18.418449,18 18.9375,18 Z"
+ transform="translate(0,1027.3622)"
+ id="rect4160"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sccssccsssssccssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" />
+ <path
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ d="m 12.499929,1033.8622 -2,2 1.500071,0 0,2 1,0 0,-2 1.499929,0 z"
+ id="path4150"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccc" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/mouse_left.svg b/systemvm/agent/noVNC/app/images/mouse_left.svg
new file mode 100644
index 0000000..ce4cca4
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/mouse_left.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="mouse_left.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="15.551515"
+ inkscape:cy="12.205592"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+ id="path6219" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+ id="path6217" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+ id="path6215" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+ id="rect6178" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/mouse_middle.svg b/systemvm/agent/noVNC/app/images/mouse_middle.svg
new file mode 100644
index 0000000..6603425
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/mouse_middle.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="mouse_middle.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="15.551515"
+ inkscape:cy="12.205592"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+ id="path6219" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+ id="path6217" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+ id="path6215" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+ id="rect6178" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/mouse_none.svg b/systemvm/agent/noVNC/app/images/mouse_none.svg
new file mode 100644
index 0000000..3e0f838
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/mouse_none.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="mouse_none.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="23.160825"
+ inkscape:cy="13.208262"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+ id="path6219" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+ id="path6217" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+ id="path6215" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+ id="rect6178" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/mouse_right.svg b/systemvm/agent/noVNC/app/images/mouse_right.svg
new file mode 100644
index 0000000..f4bad76
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/mouse_right.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="mouse_right.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="15.551515"
+ inkscape:cy="12.205592"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+ id="path6219" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+ id="path6217" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+ id="path6215" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+ id="rect6178" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/power.svg b/systemvm/agent/noVNC/app/images/power.svg
new file mode 100644
index 0000000..4925d3e
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/power.svg
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="power.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="9.3159849"
+ inkscape:cy="13.436208"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="M 9 6.8183594 C 6.3418164 8.1213032 4.5 10.849161 4.5 14 C 4.5 18.4065 8.0935666 22 12.5 22 C 16.906433 22 20.5 18.4065 20.5 14 C 20.5 10.849161 18.658184 8.1213032 16 6.8183594 L 16 9.125 C 17.514327 10.211757 18.5 11.984508 18.5 14 C 18.5 17.3256 15.825553 20 12.5 20 C 9.1744469 20 6.5 17.3256 6.5 14 C 6.5 11.984508 7.4856727 10.211757 9 9.125 L 9 6.8183594 z "
+ transform="translate(0,1027.3622)"
+ id="path6140" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 12.5,1031.8836 0,6.4786"
+ id="path6142"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/settings.svg b/systemvm/agent/noVNC/app/images/settings.svg
new file mode 100644
index 0000000..dbb2e80
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/settings.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="settings.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.627417"
+ inkscape:cx="14.69683"
+ inkscape:cy="8.8039511"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 11 3 L 11 5.1601562 A 7.5 7.5 0 0 0 8.3671875 6.2460938 L 6.84375 4.7226562 L 4.7226562 6.84375 L 6.2480469 8.3691406 A 7.5 7.5 0 0 0 5.1523438 11 L 3 11 L 3 14 L 5.1601562 14 A 7.5 7.5 0 0 0 6.2460938 16.632812 L 4.7226562 18.15625 L 6.84375 20.277344 L 8.3691406 18.751953 A 7.5 7.5 0 0 0 11 19.847656 L 11 22 L 14 22 L 14 19.839844 A 7.5 7.5 0 0 0 16.632812 18.753906 L 18.15625 20.277344 L 20.277344 18.15625 L 18.751953 16.630859 A 7.5 7.5 0 0 0 19.847656 14 L 22 14 L 22 11 L 19.839844 11 A 7.5 7.5 0 0 0 18.753906 8.3671875 L 20.277344 6.84375 L 18.15625 4.7226562 L 16.630859 6.2480469 A 7.5 7.5 0 0 0 14 5.1523438 L 14 3 L 11 3 z M 12.5 10 A 2.5 2.5 0 0 1 15 12.5 A 2.5 2.5 0 0 1 12.5 15 A 2.5 2.5 0 0 1 10 12.5 A 2.5 2.5 0 0 1 12.5 10 z "
+ transform="translate(0,1027.3622)"
+ id="rect4967" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/tab.svg b/systemvm/agent/noVNC/app/images/tab.svg
new file mode 100644
index 0000000..1ccb322
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/tab.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="tab.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="11.67335"
+ inkscape:cy="17.881696"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 3,1031.3622 0,8 2,0 0,-4 0,-4 -2,0 z m 2,4 4,4 0,-3 13,0 0,-2 -13,0 0,-3 -4,4 z"
+ id="rect5194"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path5211"
+ d="m 22,1048.3622 0,-8 -2,0 0,4 0,4 2,0 z m -2,-4 -4,-4 0,3 -13,0 0,2 13,0 0,3 4,-4 z"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/toggleextrakeys.svg b/systemvm/agent/noVNC/app/images/toggleextrakeys.svg
new file mode 100644
index 0000000..b578c0d
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/toggleextrakeys.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="extrakeys.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="15.234555"
+ inkscape:cy="9.9710826"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 8,1031.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,8.9996 c 0,2.1987 1.8012876,4 4,4 l 9,0 c 2.198712,0 4,-1.8013 4,-4 l 0,-8.9996 c 0,-2.1987 -1.801288,-4 -4,-4 z m 0,2 9,0 c 1.125307,0 2,0.8747 2,2 l 0,7.0005 c 0,1.1253 -0.874693,2 -2,2 l -9,0 c -1.1253069,0 -2,-0.8747 -2,-2 l 0,-7.0005 c 0,-1.1253 0.8746931,-2 2,-2 z"
+ id="rect5006"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ssssssssssssssssss" />
+ <g
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text4167"
+ transform="matrix(0.96021948,0,0,0.96021948,0.18921715,41.80659)">
+ <path
+ d="m 14.292969,1040.6791 -2.939453,0 -0.463868,1.3281 -1.889648,0 2.700195,-7.29 2.241211,0 2.700196,7.29 -1.889649,0 -0.458984,-1.3281 z m -2.470703,-1.3526 1.99707,0 -0.996094,-2.9004 -1.000976,2.9004 z"
+ id="path4172"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/warning.svg b/systemvm/agent/noVNC/app/images/warning.svg
new file mode 100644
index 0000000..7114f9b
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/warning.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25"
+ height="25"
+ viewBox="0 0 25 25"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="warning.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#959595"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="16.457343"
+ inkscape:cy="12.179552"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ showguides="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-nodes="true"
+ inkscape:snap-global="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4136" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1027.3622)">
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="M 12.513672 3.0019531 C 11.751609 2.9919531 11.052563 3.4242687 10.710938 4.1054688 L 3.2109375 19.105469 C 2.5461937 20.435369 3.5132277 21.9999 5 22 L 20 22 C 21.486772 21.9999 22.453806 20.435369 21.789062 19.105469 L 14.289062 4.1054688 C 13.951849 3.4330688 13.265888 3.0066531 12.513672 3.0019531 z M 12.478516 6.9804688 A 1.50015 1.50015 0 0 1 14 8.5 L 14 14.5 A 1.50015 1.50015 0 1 1 11 14.5 L 11 8.5 A 1.50015 1.50015 0 0 1 12.478516 6.9804688 z M 12.5 17 A 1.5 1.5 0 0 1 14 18.5 A 1.5 1.5 0 0 1 12.5 20 A 1.5 1.5 0 0 1 11 18.5 A 1.5 1.5 0 0 1 12.5 17 z "
+ transform="translate(0,1027.3622)"
+ id="path4208" />
+ </g>
+</svg>
diff --git a/systemvm/agent/noVNC/app/images/windows.svg b/systemvm/agent/noVNC/app/images/windows.svg
new file mode 100644
index 0000000..270405c
--- /dev/null
+++ b/systemvm/agent/noVNC/app/images/windows.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="svg2"
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:docname="windows.svg"
+ inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ x="0px"
+ y="0px"
+ viewBox="-293 384 25 23"
+ xml:space="preserve"
+ width="25"
+ height="23"><metadata
+ id="metadata21"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs19" /><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1017"
+ id="namedview17"
+ showgrid="false"
+ inkscape:pagecheckerboard="true"
+ inkscape:zoom="9.44"
+ inkscape:cx="-0.84745763"
+ inkscape:cy="12.5"
+ inkscape:window-x="2552"
+ inkscape:window-y="122"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+<style
+ type="text/css"
+ id="style2">
+ .st0{fill:#FFFFFF;}
+</style>
+<g
+ id="g14"
+ transform="matrix(1.2624869,0,0,1.3601695,73.614445,-144.84322)">
+ <g
+ id="g12">
+ <path
+ class="st0"
+ d="m -277.4,396 c -0.7,0 -1.3,0 -2,0 -0.4,0 -0.5,-0.1 -0.5,-0.5 0,-1 0,-2 0,-3 0,-0.3 0.2,-0.5 0.5,-0.5 1.3,-0.1 2.6,-0.3 3.9,-0.4 0.4,0 0.7,0.1 0.7,0.6 0,1.1 0,2.2 0,3.3 0,0.4 -0.2,0.6 -0.6,0.6 -0.7,-0.1 -1.4,-0.1 -2,-0.1 z"
+ id="path4"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" />
+ <path
+ class="st0"
+ d="m -274.9,399.3 c 0,0.6 0,1.1 0,1.7 0,0.4 -0.1,0.6 -0.6,0.6 -1.4,-0.1 -2.8,-0.3 -4.1,-0.4 -0.3,0 -0.4,-0.3 -0.4,-0.5 0,-1 0,-2 0,-3 0,-0.4 0.2,-0.5 0.6,-0.5 1.3,0 2.6,0 3.9,0 0.5,0 0.6,0.2 0.6,0.6 0,0.4 0,0.9 0,1.5 z"
+ id="path6"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" />
+ <path
+ class="st0"
+ d="m -283.5,396 c -0.6,0 -1.3,0 -1.9,0 -0.4,0 -0.6,-0.1 -0.6,-0.6 0,-0.8 0,-1.5 0,-2.3 0,-0.4 0.2,-0.6 0.6,-0.7 1.3,-0.1 2.7,-0.3 4,-0.4 0.4,0 0.5,0.1 0.5,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -0.8,0.1 -1.5,0.1 -2.1,0.1 z"
+ id="path8"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" />
+ <path
+ class="st0"
+ d="m -283.5,397 c 0.6,0 1.3,0 1.9,0 0.4,0 0.6,0.1 0.6,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -1.3,-0.1 -2.7,-0.3 -4,-0.4 -0.4,0 -0.6,-0.2 -0.6,-0.7 0,-0.7 0,-1.5 0,-2.2 0,-0.5 0.2,-0.7 0.7,-0.7 0.6,0.1 1.2,0.1 1.9,0.1 z"
+ id="path10"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" />
+ </g>
+</g>
+</svg>
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/cs.json b/systemvm/agent/noVNC/app/locale/cs.json
new file mode 100644
index 0000000..589145e
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/cs.json
@@ -0,0 +1,71 @@
+{
+ "Connecting...": "Připojení...",
+ "Disconnecting...": "Odpojení...",
+ "Reconnecting...": "Obnova připojení...",
+ "Internal error": "Vnitřní chyba",
+ "Must set host": "Hostitel musí být nastavení",
+ "Connected (encrypted) to ": "Připojení (šifrované) k ",
+ "Connected (unencrypted) to ": "Připojení (nešifrované) k ",
+ "Something went wrong, connection is closed": "Něco se pokazilo, odpojeno",
+ "Failed to connect to server": "Chyba připojení k serveru",
+ "Disconnected": "Odpojeno",
+ "New connection has been rejected with reason: ": "Nové připojení bylo odmítnuto s odůvodněním: ",
+ "New connection has been rejected": "Nové připojení bylo odmítnuto",
+ "Password is required": "Je vyžadováno heslo",
+ "noVNC encountered an error:": "noVNC narazilo na chybu:",
+ "Hide/Show the control bar": "Skrýt/zobrazit ovládací panel",
+ "Move/Drag Viewport": "Přesunout/přetáhnout výřez",
+ "viewport drag": "přesun výřezu",
+ "Active Mouse Button": "Aktivní tlačítka myši",
+ "No mousebutton": "Žádné",
+ "Left mousebutton": "Levé tlačítko myši",
+ "Middle mousebutton": "Prostřední tlačítko myši",
+ "Right mousebutton": "Pravé tlačítko myši",
+ "Keyboard": "Klávesnice",
+ "Show Keyboard": "Zobrazit klávesnici",
+ "Extra keys": "Extra klávesy",
+ "Show Extra Keys": "Zobrazit extra klávesy",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Přepnout Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "Přepnout Alt",
+ "Send Tab": "Odeslat tabulátor",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Odeslat Esc",
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+ "Send Ctrl-Alt-Del": "Poslat Ctrl-Alt-Del",
+ "Shutdown/Reboot": "Vypnutí/Restart",
+ "Shutdown/Reboot...": "Vypnutí/Restart...",
+ "Power": "Napájení",
+ "Shutdown": "Vypnout",
+ "Reboot": "Restart",
+ "Reset": "Reset",
+ "Clipboard": "Schránka",
+ "Clear": "Vymazat",
+ "Fullscreen": "Celá obrazovka",
+ "Settings": "Nastavení",
+ "Shared Mode": "Sdílený režim",
+ "View Only": "Pouze prohlížení",
+ "Clip to Window": "Přizpůsobit oknu",
+ "Scaling Mode:": "Přizpůsobení velikosti",
+ "None": "Žádné",
+ "Local Scaling": "Místní",
+ "Remote Resizing": "Vzdálené",
+ "Advanced": "Pokročilé",
+ "Repeater ID:": "ID opakovače",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Šifrování:",
+ "Host:": "Hostitel:",
+ "Port:": "Port:",
+ "Path:": "Cesta",
+ "Automatic Reconnect": "Automatická obnova připojení",
+ "Reconnect Delay (ms):": "Zpoždění připojení (ms)",
+ "Show Dot when No Cursor": "Tečka místo chybějícího kurzoru myši",
+ "Logging:": "Logování:",
+ "Disconnect": "Odpojit",
+ "Connect": "Připojit",
+ "Password:": "Heslo",
+ "Send Password": "Odeslat heslo",
+ "Cancel": "Zrušit"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/de.json b/systemvm/agent/noVNC/app/locale/de.json
new file mode 100644
index 0000000..62e7336
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/de.json
@@ -0,0 +1,69 @@
+{
+ "Connecting...": "Verbinden...",
+ "Disconnecting...": "Verbindung trennen...",
+ "Reconnecting...": "Verbindung wiederherstellen...",
+ "Internal error": "Interner Fehler",
+ "Must set host": "Richten Sie den Server ein",
+ "Connected (encrypted) to ": "Verbunden mit (verschlüsselt) ",
+ "Connected (unencrypted) to ": "Verbunden mit (unverschlüsselt) ",
+ "Something went wrong, connection is closed": "Etwas lief schief, Verbindung wurde getrennt",
+ "Disconnected": "Verbindung zum Server getrennt",
+ "New connection has been rejected with reason: ": "Verbindung wurde aus folgendem Grund abgelehnt: ",
+ "New connection has been rejected": "Verbindung wurde abgelehnt",
+ "Password is required": "Passwort ist erforderlich",
+ "noVNC encountered an error:": "Ein Fehler ist aufgetreten:",
+ "Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen",
+ "Move/Drag Viewport": "Ansichtsfenster verschieben/ziehen",
+ "viewport drag": "Ansichtsfenster ziehen",
+ "Active Mouse Button": "Aktive Maustaste",
+ "No mousebutton": "Keine Maustaste",
+ "Left mousebutton": "Linke Maustaste",
+ "Middle mousebutton": "Mittlere Maustaste",
+ "Right mousebutton": "Rechte Maustaste",
+ "Keyboard": "Tastatur",
+ "Show Keyboard": "Tastatur anzeigen",
+ "Extra keys": "Zusatztasten",
+ "Show Extra Keys": "Zusatztasten anzeigen",
+ "Ctrl": "Strg",
+ "Toggle Ctrl": "Strg umschalten",
+ "Alt": "Alt",
+ "Toggle Alt": "Alt umschalten",
+ "Send Tab": "Tab senden",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Escape senden",
+ "Ctrl+Alt+Del": "Strg+Alt+Entf",
+ "Send Ctrl-Alt-Del": "Strg+Alt+Entf senden",
+ "Shutdown/Reboot": "Herunterfahren/Neustarten",
+ "Shutdown/Reboot...": "Herunterfahren/Neustarten...",
+ "Power": "Energie",
+ "Shutdown": "Herunterfahren",
+ "Reboot": "Neustarten",
+ "Reset": "Zurücksetzen",
+ "Clipboard": "Zwischenablage",
+ "Clear": "Löschen",
+ "Fullscreen": "Vollbild",
+ "Settings": "Einstellungen",
+ "Shared Mode": "Geteilter Modus",
+ "View Only": "Nur betrachten",
+ "Clip to Window": "Auf Fenster begrenzen",
+ "Scaling Mode:": "Skalierungsmodus:",
+ "None": "Keiner",
+ "Local Scaling": "Lokales skalieren",
+ "Remote Resizing": "Serverseitiges skalieren",
+ "Advanced": "Erweitert",
+ "Repeater ID:": "Repeater ID:",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Verschlüsselt",
+ "Host:": "Server:",
+ "Port:": "Port:",
+ "Path:": "Pfad:",
+ "Automatic Reconnect": "Automatisch wiederverbinden",
+ "Reconnect Delay (ms):": "Wiederverbindungsverzögerung (ms):",
+ "Logging:": "Protokollierung:",
+ "Disconnect": "Verbindung trennen",
+ "Connect": "Verbinden",
+ "Password:": "Passwort:",
+ "Cancel": "Abbrechen",
+ "Canvas not supported.": "Canvas nicht unterstützt."
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/el.json b/systemvm/agent/noVNC/app/locale/el.json
new file mode 100644
index 0000000..f801251
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/el.json
@@ -0,0 +1,69 @@
+{
+ "Connecting...": "Συνδέεται...",
+ "Disconnecting...": "Aποσυνδέεται...",
+ "Reconnecting...": "Επανασυνδέεται...",
+ "Internal error": "Εσωτερικό σφάλμα",
+ "Must set host": "Πρέπει να οριστεί ο διακομιστής",
+ "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
+ "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
+ "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε",
+ "Disconnected": "Αποσυνδέθηκε",
+ "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
+ "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
+ "Password is required": "Απαιτείται ο κωδικός πρόσβασης",
+ "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
+ "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
+ "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
+ "viewport drag": "σύρσιμο θεατού πεδίου",
+ "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
+ "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
+ "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
+ "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
+ "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
+ "Keyboard": "Πληκτρολόγιο",
+ "Show Keyboard": "Εμφάνιση Πληκτρολογίου",
+ "Extra keys": "Επιπλέον πλήκτρα",
+ "Show Extra Keys": "Εμφάνιση Επιπλέον Πλήκτρων",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Εναλλαγή Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "Εναλλαγή Alt",
+ "Send Tab": "Αποστολή Tab",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Αποστολή Escape",
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+ "Send Ctrl-Alt-Del": "Αποστολή Ctrl-Alt-Del",
+ "Shutdown/Reboot": "Κλείσιμο/Επανεκκίνηση",
+ "Shutdown/Reboot...": "Κλείσιμο/Επανεκκίνηση...",
+ "Power": "Απενεργοποίηση",
+ "Shutdown": "Κλείσιμο",
+ "Reboot": "Επανεκκίνηση",
+ "Reset": "Επαναφορά",
+ "Clipboard": "Πρόχειρο",
+ "Clear": "Καθάρισμα",
+ "Fullscreen": "Πλήρης Οθόνη",
+ "Settings": "Ρυθμίσεις",
+ "Shared Mode": "Κοινόχρηστη Λειτουργία",
+ "View Only": "Μόνο Θέαση",
+ "Clip to Window": "Αποκοπή στο όριο του Παράθυρου",
+ "Scaling Mode:": "Λειτουργία Κλιμάκωσης:",
+ "None": "Καμία",
+ "Local Scaling": "Τοπική Κλιμάκωση",
+ "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
+ "Advanced": "Για προχωρημένους",
+ "Repeater ID:": "Repeater ID:",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Κρυπτογράφηση",
+ "Host:": "Όνομα διακομιστή:",
+ "Port:": "Πόρτα διακομιστή:",
+ "Path:": "Διαδρομή:",
+ "Automatic Reconnect": "Αυτόματη επανασύνδεση",
+ "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):",
+ "Logging:": "Καταγραφή:",
+ "Disconnect": "Αποσύνδεση",
+ "Connect": "Σύνδεση",
+ "Password:": "Κωδικός Πρόσβασης:",
+ "Cancel": "Ακύρωση",
+ "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/es.json b/systemvm/agent/noVNC/app/locale/es.json
new file mode 100644
index 0000000..23f23f4
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/es.json
@@ -0,0 +1,68 @@
+{
+ "Connecting...": "Conectando...",
+ "Connected (encrypted) to ": "Conectado (con encriptación) a",
+ "Connected (unencrypted) to ": "Conectado (sin encriptación) a",
+ "Disconnecting...": "Desconectando...",
+ "Disconnected": "Desconectado",
+ "Must set host": "Debes configurar el host",
+ "Reconnecting...": "Reconectando...",
+ "Password is required": "Contraseña es obligatoria",
+ "Disconnect timeout": "Tiempo de desconexión agotado",
+ "noVNC encountered an error:": "noVNC ha encontrado un error:",
+ "Hide/Show the control bar": "Ocultar/Mostrar la barra de control",
+ "Move/Drag Viewport": "Mover/Arrastrar la ventana",
+ "viewport drag": "Arrastrar la ventana",
+ "Active Mouse Button": "Botón activo del ratón",
+ "No mousebutton": "Ningún botón del ratón",
+ "Left mousebutton": "Botón izquierdo del ratón",
+ "Middle mousebutton": "Botón central del ratón",
+ "Right mousebutton": "Botón derecho del ratón",
+ "Keyboard": "Teclado",
+ "Show Keyboard": "Mostrar teclado",
+ "Extra keys": "Teclas adicionales",
+ "Show Extra Keys": "Mostrar Teclas Adicionales",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Pulsar/Soltar Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "Pulsar/Soltar Alt",
+ "Send Tab": "Enviar Tabulación",
+ "Tab": "Tabulación",
+ "Esc": "Esc",
+ "Send Escape": "Enviar Escape",
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+ "Send Ctrl-Alt-Del": "Enviar Ctrl+Alt+Del",
+ "Shutdown/Reboot": "Apagar/Reiniciar",
+ "Shutdown/Reboot...": "Apagar/Reiniciar...",
+ "Power": "Encender",
+ "Shutdown": "Apagar",
+ "Reboot": "Reiniciar",
+ "Reset": "Restablecer",
+ "Clipboard": "Portapapeles",
+ "Clear": "Vaciar",
+ "Fullscreen": "Pantalla Completa",
+ "Settings": "Configuraciones",
+ "Shared Mode": "Modo Compartido",
+ "View Only": "Solo visualización",
+ "Clip to Window": "Recortar al tamaño de la ventana",
+ "Scaling Mode:": "Modo de escalado:",
+ "None": "Ninguno",
+ "Local Scaling": "Escalado Local",
+ "Local Downscaling": "Reducción de escala local",
+ "Remote Resizing": "Cambio de tamaño remoto",
+ "Advanced": "Avanzado",
+ "Local Cursor": "Cursor Local",
+ "Repeater ID:": "ID del Repetidor",
+ "WebSocket": "WebSocket",
+ "Encrypt": "",
+ "Host:": "Host",
+ "Port:": "Puesto",
+ "Path:": "Ruta",
+ "Automatic Reconnect": "Reconexión automática",
+ "Reconnect Delay (ms):": "Retraso en la reconexión (ms)",
+ "Logging:": "Logging",
+ "Disconnect": "Desconectar",
+ "Connect": "Conectar",
+ "Password:": "Contraseña",
+ "Cancel": "Cancelar",
+ "Canvas not supported.": "Canvas no está soportado"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/ko.json b/systemvm/agent/noVNC/app/locale/ko.json
new file mode 100644
index 0000000..e4ecddc
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/ko.json
@@ -0,0 +1,70 @@
+{
+ "Connecting...": "연결중...",
+ "Disconnecting...": "연결 해제중...",
+ "Reconnecting...": "재연결중...",
+ "Internal error": "내부 오류",
+ "Must set host": "호스트는 설정되어야 합니다.",
+ "Connected (encrypted) to ": "다음과 (암호화되어) 연결되었습니다:",
+ "Connected (unencrypted) to ": "다음과 (암호화 없이) 연결되었습니다:",
+ "Something went wrong, connection is closed": "무언가 잘못되었습니다, 연결이 닫혔습니다.",
+ "Failed to connect to server": "서버에 연결하지 못했습니다.",
+ "Disconnected": "연결이 해제되었습니다.",
+ "New connection has been rejected with reason: ": "새 연결이 다음 이유로 거부되었습니다:",
+ "New connection has been rejected": "새 연결이 거부되었습니다.",
+ "Password is required": "비밀번호가 필요합니다.",
+ "noVNC encountered an error:": "noVNC에 오류가 발생했습니다:",
+ "Hide/Show the control bar": "컨트롤 바 숨기기/보이기",
+ "Move/Drag Viewport": "움직이기/드래그 뷰포트",
+ "viewport drag": "뷰포트 드래그",
+ "Active Mouse Button": "마우스 버튼 활성화",
+ "No mousebutton": "마우스 버튼 없음",
+ "Left mousebutton": "왼쪽 마우스 버튼",
+ "Middle mousebutton": "중간 마우스 버튼",
+ "Right mousebutton": "오른쪽 마우스 버튼",
+ "Keyboard": "키보드",
+ "Show Keyboard": "키보드 보이기",
+ "Extra keys": "기타 키들",
+ "Show Extra Keys": "기타 키들 보이기",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Ctrl 켜기/끄기",
+ "Alt": "Alt",
+ "Toggle Alt": "Alt 켜기/끄기",
+ "Send Tab": "Tab 보내기",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Esc 보내기",
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+ "Send Ctrl-Alt-Del": "Ctrl+Alt+Del 보내기",
+ "Shutdown/Reboot": "셧다운/리붓",
+ "Shutdown/Reboot...": "셧다운/리붓...",
+ "Power": "전원",
+ "Shutdown": "셧다운",
+ "Reboot": "리붓",
+ "Reset": "리셋",
+ "Clipboard": "클립보드",
+ "Clear": "지우기",
+ "Fullscreen": "전체화면",
+ "Settings": "설정",
+ "Shared Mode": "공유 모드",
+ "View Only": "보기 전용",
+ "Clip to Window": "창에 클립",
+ "Scaling Mode:": "스케일링 모드:",
+ "None": "없음",
+ "Local Scaling": "로컬 스케일링",
+ "Remote Resizing": "원격 크기 조절",
+ "Advanced": "고급",
+ "Repeater ID:": "중계 ID",
+ "WebSocket": "웹소켓",
+ "Encrypt": "암호화",
+ "Host:": "호스트:",
+ "Port:": "포트:",
+ "Path:": "위치:",
+ "Automatic Reconnect": "자동 재연결",
+ "Reconnect Delay (ms):": "재연결 지연 시간 (ms)",
+ "Logging:": "로깅",
+ "Disconnect": "연결 해제",
+ "Connect": "연결",
+ "Password:": "비밀번호:",
+ "Send Password": "비밀번호 전송",
+ "Cancel": "취소"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/nl.json b/systemvm/agent/noVNC/app/locale/nl.json
new file mode 100644
index 0000000..0cdcc92
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/nl.json
@@ -0,0 +1,73 @@
+{
+ "Connecting...": "Verbinden...",
+ "Disconnecting...": "Verbinding verbreken...",
+ "Reconnecting...": "Opnieuw verbinding maken...",
+ "Internal error": "Interne fout",
+ "Must set host": "Host moeten worden ingesteld",
+ "Connected (encrypted) to ": "Verbonden (versleuteld) met ",
+ "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ",
+ "Something went wrong, connection is closed": "Er iets fout gelopen, verbinding werd verbroken",
+ "Failed to connect to server": "Verbinding maken met server is mislukt",
+ "Disconnected": "Verbinding verbroken",
+ "New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd omwille van de volgende reden: ",
+ "New connection has been rejected": "Nieuwe verbinding is geweigerd",
+ "Password is required": "Wachtwoord is vereist",
+ "noVNC encountered an error:": "noVNC heeft een fout bemerkt:",
+ "Hide/Show the control bar": "Verberg/Toon de bedieningsbalk",
+ "Move/Drag Viewport": "Verplaats/Versleep Kijkvenster",
+ "viewport drag": "kijkvenster slepen",
+ "Active Mouse Button": "Actieve Muisknop",
+ "No mousebutton": "Geen muisknop",
+ "Left mousebutton": "Linker muisknop",
+ "Middle mousebutton": "Middelste muisknop",
+ "Right mousebutton": "Rechter muisknop",
+ "Keyboard": "Toetsenbord",
+ "Show Keyboard": "Toon Toetsenbord",
+ "Extra keys": "Extra toetsen",
+ "Show Extra Keys": "Toon Extra Toetsen",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Ctrl omschakelen",
+ "Alt": "Alt",
+ "Toggle Alt": "Alt omschakelen",
+ "Toggle Windows": "Windows omschakelen",
+ "Windows": "Windows",
+ "Send Tab": "Tab Sturen",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Escape Sturen",
+ "Ctrl+Alt+Del": "Ctrl-Alt-Del",
+ "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Sturen",
+ "Shutdown/Reboot": "Uitschakelen/Herstarten",
+ "Shutdown/Reboot...": "Uitschakelen/Herstarten...",
+ "Power": "Systeem",
+ "Shutdown": "Uitschakelen",
+ "Reboot": "Herstarten",
+ "Reset": "Resetten",
+ "Clipboard": "Klembord",
+ "Clear": "Wissen",
+ "Fullscreen": "Volledig Scherm",
+ "Settings": "Instellingen",
+ "Shared Mode": "Gedeelde Modus",
+ "View Only": "Alleen Kijken",
+ "Clip to Window": "Randen buiten venster afsnijden",
+ "Scaling Mode:": "Schaalmodus:",
+ "None": "Geen",
+ "Local Scaling": "Lokaal Schalen",
+ "Remote Resizing": "Op Afstand Formaat Wijzigen",
+ "Advanced": "Geavanceerd",
+ "Repeater ID:": "Repeater ID:",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Versleutelen",
+ "Host:": "Host:",
+ "Port:": "Poort:",
+ "Path:": "Pad:",
+ "Automatic Reconnect": "Automatisch Opnieuw Verbinden",
+ "Reconnect Delay (ms):": "Vertraging voor Opnieuw Verbinden (ms):",
+ "Show Dot when No Cursor": "Geef stip weer indien geen cursor",
+ "Logging:": "Logmeldingen:",
+ "Disconnect": "Verbinding verbreken",
+ "Connect": "Verbinden",
+ "Password:": "Wachtwoord:",
+ "Send Password": "Verzend Wachtwoord:",
+ "Cancel": "Annuleren"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/pl.json b/systemvm/agent/noVNC/app/locale/pl.json
new file mode 100644
index 0000000..006ac7a
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/pl.json
@@ -0,0 +1,69 @@
+{
+ "Connecting...": "Łączenie...",
+ "Disconnecting...": "Rozłączanie...",
+ "Reconnecting...": "Łączenie...",
+ "Internal error": "Błąd wewnętrzny",
+ "Must set host": "Host i port są wymagane",
+ "Connected (encrypted) to ": "Połączenie (szyfrowane) z ",
+ "Connected (unencrypted) to ": "Połączenie (nieszyfrowane) z ",
+ "Something went wrong, connection is closed": "Coś poszło źle, połączenie zostało zamknięte",
+ "Disconnected": "Rozłączony",
+ "New connection has been rejected with reason: ": "Nowe połączenie zostało odrzucone z powodu: ",
+ "New connection has been rejected": "Nowe połączenie zostało odrzucone",
+ "Password is required": "Hasło jest wymagane",
+ "noVNC encountered an error:": "noVNC napotkało błąd:",
+ "Hide/Show the control bar": "Pokaż/Ukryj pasek ustawień",
+ "Move/Drag Viewport": "Ruszaj/Przeciągaj Viewport",
+ "viewport drag": "przeciągnij viewport",
+ "Active Mouse Button": "Aktywny Przycisk Myszy",
+ "No mousebutton": "Brak przycisku myszy",
+ "Left mousebutton": "Lewy przycisk myszy",
+ "Middle mousebutton": "Środkowy przycisk myszy",
+ "Right mousebutton": "Prawy przycisk myszy",
+ "Keyboard": "Klawiatura",
+ "Show Keyboard": "Pokaż klawiaturę",
+ "Extra keys": "Przyciski dodatkowe",
+ "Show Extra Keys": "Pokaż przyciski dodatkowe",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Przełącz Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "Przełącz Alt",
+ "Send Tab": "Wyślij Tab",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Wyślij Escape",
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+ "Send Ctrl-Alt-Del": "Wyślij Ctrl-Alt-Del",
+ "Shutdown/Reboot": "Wyłącz/Uruchom ponownie",
+ "Shutdown/Reboot...": "Wyłącz/Uruchom ponownie...",
+ "Power": "Włączony",
+ "Shutdown": "Wyłącz",
+ "Reboot": "Uruchom ponownie",
+ "Reset": "Resetuj",
+ "Clipboard": "Schowek",
+ "Clear": "Wyczyść",
+ "Fullscreen": "Pełny ekran",
+ "Settings": "Ustawienia",
+ "Shared Mode": "Tryb Współdzielenia",
+ "View Only": "Tylko Podgląd",
+ "Clip to Window": "Przytnij do Okna",
+ "Scaling Mode:": "Tryb Skalowania:",
+ "None": "Brak",
+ "Local Scaling": "Skalowanie lokalne",
+ "Remote Resizing": "Skalowanie zdalne",
+ "Advanced": "Zaawansowane",
+ "Repeater ID:": "ID Repeatera:",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Szyfrowanie",
+ "Host:": "Host:",
+ "Port:": "Port:",
+ "Path:": "Ścieżka:",
+ "Automatic Reconnect": "Automatycznie wznawiaj połączenie",
+ "Reconnect Delay (ms):": "Opóźnienie wznawiania (ms):",
+ "Logging:": "Poziom logowania:",
+ "Disconnect": "Rozłącz",
+ "Connect": "Połącz",
+ "Password:": "Hasło:",
+ "Cancel": "Anuluj",
+ "Canvas not supported.": "Element Canvas nie jest wspierany."
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/ru.json b/systemvm/agent/noVNC/app/locale/ru.json
new file mode 100644
index 0000000..52e57f3
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/ru.json
@@ -0,0 +1,73 @@
+{
+ "Connecting...": "Подключение...",
+ "Disconnecting...": "Отключение...",
+ "Reconnecting...": "Переподключение...",
+ "Internal error": "Внутренняя ошибка",
+ "Must set host": "Задайте имя сервера или IP",
+ "Connected (encrypted) to ": "Подключено (с шифрованием) к ",
+ "Connected (unencrypted) to ": "Подключено (без шифрования) к ",
+ "Something went wrong, connection is closed": "Что-то пошло не так, подключение разорвано",
+ "Failed to connect to server": "Ошибка подключения к серверу",
+ "Disconnected": "Отключено",
+ "New connection has been rejected with reason: ": "Подключиться не удалось: ",
+ "New connection has been rejected": "Подключиться не удалось",
+ "Password is required": "Требуется пароль",
+ "noVNC encountered an error:": "Ошибка noVNC: ",
+ "Hide/Show the control bar": "Скрыть/Показать контрольную панель",
+ "Move/Drag Viewport": "Переместить окно",
+ "viewport drag": "Переместить окно",
+ "Active Mouse Button": "Активировать кнопки мыши",
+ "No mousebutton": "Отключить кнопки мыши",
+ "Left mousebutton": "Левая кнопка мыши",
+ "Middle mousebutton": "Средняя кнопка мыши",
+ "Right mousebutton": "Правая кнопка мыши",
+ "Keyboard": "Клавиатура",
+ "Show Keyboard": "Показать клавиатуру",
+ "Extra keys": "Доп. кнопки",
+ "Show Extra Keys": "Показать дополнительные кнопки",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Передать нажатие Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "Передать нажатие Alt",
+ "Toggle Windows": "Переключение вкладок",
+ "Windows": "Вкладка",
+ "Send Tab": "Передать нажатие Tab",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Передать нажатие Escape",
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+ "Send Ctrl-Alt-Del": "Передать нажатие Ctrl-Alt-Del",
+ "Shutdown/Reboot": "Выключить/Перезагрузить",
+ "Shutdown/Reboot...": "Выключить/Перезагрузить...",
+ "Power": "Питание",
+ "Shutdown": "Выключить",
+ "Reboot": "Перезагрузить",
+ "Reset": "Сброс",
+ "Clipboard": "Буфер обмена",
+ "Clear": "Очистить",
+ "Fullscreen": "Во весь экран",
+ "Settings": "Настройки",
+ "Shared Mode": "Общий режим",
+ "View Only": "Просмотр",
+ "Clip to Window": "В окно",
+ "Scaling Mode:": "Масштаб:",
+ "None": "Нет",
+ "Local Scaling": "Локльный масштаб",
+ "Remote Resizing": "Удаленный масштаб",
+ "Advanced": "Дополнительно",
+ "Repeater ID:": "Идентификатор ID:",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Шифрование",
+ "Host:": "Сервер:",
+ "Port:": "Порт:",
+ "Path:": "Путь:",
+ "Automatic Reconnect": "Автоматическое переподключение",
+ "Reconnect Delay (ms):": "Задержка переподключения (мс):",
+ "Show Dot when No Cursor": "Показать точку вместо курсора",
+ "Logging:": "Лог:",
+ "Disconnect": "Отключение",
+ "Connect": "Подключение",
+ "Password:": "Пароль:",
+ "Send Password": "Пароль: ",
+ "Cancel": "Выход"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/sv.json b/systemvm/agent/noVNC/app/locale/sv.json
new file mode 100644
index 0000000..d49ea54
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/sv.json
@@ -0,0 +1,73 @@
+{
+ "Connecting...": "Ansluter...",
+ "Disconnecting...": "Kopplar ner...",
+ "Reconnecting...": "Återansluter...",
+ "Internal error": "Internt fel",
+ "Must set host": "Du måste specifiera en värd",
+ "Connected (encrypted) to ": "Ansluten (krypterat) till ",
+ "Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
+ "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",
+ "Failed to connect to server": "Misslyckades att ansluta till servern",
+ "Disconnected": "Frånkopplad",
+ "New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
+ "New connection has been rejected": "Ny anslutning har blivit nekad",
+ "Password is required": "Lösenord krävs",
+ "noVNC encountered an error:": "noVNC stötte på ett problem:",
+ "Hide/Show the control bar": "Göm/Visa kontrollbaren",
+ "Move/Drag Viewport": "Flytta/Dra Vyn",
+ "viewport drag": "dra vy",
+ "Active Mouse Button": "Aktiv musknapp",
+ "No mousebutton": "Ingen musknapp",
+ "Left mousebutton": "Vänster musknapp",
+ "Middle mousebutton": "Mitten-musknapp",
+ "Right mousebutton": "Höger musknapp",
+ "Keyboard": "Tangentbord",
+ "Show Keyboard": "Visa Tangentbord",
+ "Extra keys": "Extraknappar",
+ "Show Extra Keys": "Visa Extraknappar",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Växla Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "Växla Alt",
+ "Toggle Windows": "Växla Windows",
+ "Windows": "Windows",
+ "Send Tab": "Skicka Tab",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "Skicka Escape",
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+ "Send Ctrl-Alt-Del": "Skicka Ctrl-Alt-Del",
+ "Shutdown/Reboot": "Stäng av/Boota om",
+ "Shutdown/Reboot...": "Stäng av/Boota om...",
+ "Power": "Ström",
+ "Shutdown": "Stäng av",
+ "Reboot": "Boota om",
+ "Reset": "Återställ",
+ "Clipboard": "Urklipp",
+ "Clear": "Rensa",
+ "Fullscreen": "Fullskärm",
+ "Settings": "Inställningar",
+ "Shared Mode": "Delat Läge",
+ "View Only": "Endast Visning",
+ "Clip to Window": "Begränsa till Fönster",
+ "Scaling Mode:": "Skalningsläge:",
+ "None": "Ingen",
+ "Local Scaling": "Lokal Skalning",
+ "Remote Resizing": "Ändra Storlek",
+ "Advanced": "Avancerat",
+ "Repeater ID:": "Repeater-ID:",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Kryptera",
+ "Host:": "Värd:",
+ "Port:": "Port:",
+ "Path:": "Sökväg:",
+ "Automatic Reconnect": "Automatisk Återanslutning",
+ "Reconnect Delay (ms):": "Fördröjning (ms):",
+ "Show Dot when No Cursor": "Visa prick när ingen muspekare finns",
+ "Logging:": "Loggning:",
+ "Disconnect": "Koppla från",
+ "Connect": "Anslut",
+ "Password:": "Lösenord:",
+ "Send Password": "Skicka lösenord",
+ "Cancel": "Avbryt"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/tr.json b/systemvm/agent/noVNC/app/locale/tr.json
new file mode 100644
index 0000000..451c1b8
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/tr.json
@@ -0,0 +1,69 @@
+{
+ "Connecting...": "Bağlanıyor...",
+ "Disconnecting...": "Bağlantı kesiliyor...",
+ "Reconnecting...": "Yeniden bağlantı kuruluyor...",
+ "Internal error": "İç hata",
+ "Must set host": "Sunucuyu kur",
+ "Connected (encrypted) to ": "Bağlı (şifrelenmiş)",
+ "Connected (unencrypted) to ": "Bağlandı (şifrelenmemiş)",
+ "Something went wrong, connection is closed": "Bir şeyler ters gitti, bağlantı kesildi",
+ "Disconnected": "Bağlantı kesildi",
+ "New connection has been rejected with reason: ": "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: ",
+ "New connection has been rejected": "Bağlantı reddedildi",
+ "Password is required": "Şifre gerekli",
+ "noVNC encountered an error:": "Bir hata oluştu:",
+ "Hide/Show the control bar": "Denetim masasını Gizle/Göster",
+ "Move/Drag Viewport": "Görünümü Taşı/Sürükle",
+ "viewport drag": "Görüntü penceresini sürükle",
+ "Active Mouse Button": "Aktif Fare Düğmesi",
+ "No mousebutton": "Fare düğmesi yok",
+ "Left mousebutton": "Farenin sol düğmesi",
+ "Middle mousebutton": "Farenin orta düğmesi",
+ "Right mousebutton": "Farenin sağ düğmesi",
+ "Keyboard": "Klavye",
+ "Show Keyboard": "Klavye Düzenini Göster",
+ "Extra keys": "Ekstra tuşlar",
+ "Show Extra Keys": "Ekstra tuşları göster",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "Ctrl Değiştir ",
+ "Alt": "Alt",
+ "Toggle Alt": "Alt Değiştir",
+ "Send Tab": "Sekme Gönder",
+ "Tab": "Sekme",
+ "Esc": "Esc",
+ "Send Escape": "Boşluk Gönder",
+ "Ctrl+Alt+Del": "Ctrl + Alt + Del",
+ "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Gönder",
+ "Shutdown/Reboot": "Kapat/Yeniden Başlat",
+ "Shutdown/Reboot...": "Kapat/Yeniden Başlat...",
+ "Power": "Güç",
+ "Shutdown": "Kapat",
+ "Reboot": "Yeniden Başlat",
+ "Reset": "Sıfırla",
+ "Clipboard": "Pano",
+ "Clear": "Temizle",
+ "Fullscreen": "Tam Ekran",
+ "Settings": "Ayarlar",
+ "Shared Mode": "Paylaşım Modu",
+ "View Only": "Sadece Görüntüle",
+ "Clip to Window": "Pencereye Tıkla",
+ "Scaling Mode:": "Ölçekleme Modu:",
+ "None": "Bilinmeyen",
+ "Local Scaling": "Yerel Ölçeklendirme",
+ "Remote Resizing": "Uzaktan Yeniden Boyutlandırma",
+ "Advanced": "Gelişmiş",
+ "Repeater ID:": "Tekralayıcı ID:",
+ "WebSocket": "WebSocket",
+ "Encrypt": "Şifrele",
+ "Host:": "Ana makine:",
+ "Port:": "Port:",
+ "Path:": "Yol:",
+ "Automatic Reconnect": "Otomatik Yeniden Bağlan",
+ "Reconnect Delay (ms):": "Yeniden Bağlanma Süreci (ms):",
+ "Logging:": "Giriş yapılıyor:",
+ "Disconnect": "Bağlantıyı Kes",
+ "Connect": "Bağlan",
+ "Password:": "Parola:",
+ "Cancel": "Vazgeç",
+ "Canvas not supported.": "Tuval desteklenmiyor."
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/zh_CN.json b/systemvm/agent/noVNC/app/locale/zh_CN.json
new file mode 100644
index 0000000..b669956
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/zh_CN.json
@@ -0,0 +1,69 @@
+{
+ "Connecting...": "链接中...",
+ "Disconnecting...": "正在中断连接...",
+ "Reconnecting...": "重新链接中...",
+ "Internal error": "内部错误",
+ "Must set host": "请提供主机名",
+ "Connected (encrypted) to ": "已加密链接到",
+ "Connected (unencrypted) to ": "未加密链接到",
+ "Something went wrong, connection is closed": "发生错误,链接已关闭",
+ "Failed to connect to server": "无法链接到服务器",
+ "Disconnected": "链接已中断",
+ "New connection has been rejected with reason: ": "链接被拒绝,原因:",
+ "New connection has been rejected": "链接被拒绝",
+ "Password is required": "请提供密码",
+ "noVNC encountered an error:": "noVNC 遇到一个错误:",
+ "Hide/Show the control bar": "显示/隐藏控制列",
+ "Move/Drag Viewport": "拖放显示范围",
+ "viewport drag": "显示范围拖放",
+ "Active Mouse Button": "启动鼠标按鍵",
+ "No mousebutton": "禁用鼠标按鍵",
+ "Left mousebutton": "鼠标左鍵",
+ "Middle mousebutton": "鼠标中鍵",
+ "Right mousebutton": "鼠标右鍵",
+ "Keyboard": "键盘",
+ "Show Keyboard": "显示键盘",
+ "Extra keys": "额外按键",
+ "Show Extra Keys": "显示额外按键",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "切换 Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "切换 Alt",
+ "Send Tab": "发送 Tab 键",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "发送 Escape 键",
+ "Ctrl+Alt+Del": "Ctrl-Alt-Del",
+ "Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键",
+ "Shutdown/Reboot": "关机/重新启动",
+ "Shutdown/Reboot...": "关机/重新启动...",
+ "Power": "电源",
+ "Shutdown": "关机",
+ "Reboot": "重新启动",
+ "Reset": "重置",
+ "Clipboard": "剪贴板",
+ "Clear": "清除",
+ "Fullscreen": "全屏幕",
+ "Settings": "设置",
+ "Shared Mode": "分享模式",
+ "View Only": "仅检视",
+ "Clip to Window": "限制/裁切窗口大小",
+ "Scaling Mode:": "缩放模式:",
+ "None": "无",
+ "Local Scaling": "本地缩放",
+ "Remote Resizing": "远程调整大小",
+ "Advanced": "高级",
+ "Repeater ID:": "中继站 ID",
+ "WebSocket": "WebSocket",
+ "Encrypt": "加密",
+ "Host:": "主机:",
+ "Port:": "端口:",
+ "Path:": "路径:",
+ "Automatic Reconnect": "自动重新链接",
+ "Reconnect Delay (ms):": "重新链接间隔 (ms):",
+ "Logging:": "日志级别:",
+ "Disconnect": "终端链接",
+ "Connect": "链接",
+ "Password:": "密码:",
+ "Cancel": "取消"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/zh_TW.json b/systemvm/agent/noVNC/app/locale/zh_TW.json
new file mode 100644
index 0000000..8ddf813
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/zh_TW.json
@@ -0,0 +1,69 @@
+{
+ "Connecting...": "連線中...",
+ "Disconnecting...": "正在中斷連線...",
+ "Reconnecting...": "重新連線中...",
+ "Internal error": "內部錯誤",
+ "Must set host": "請提供主機資訊",
+ "Connected (encrypted) to ": "已加密連線到",
+ "Connected (unencrypted) to ": "未加密連線到",
+ "Something went wrong, connection is closed": "發生錯誤,連線已關閉",
+ "Failed to connect to server": "無法連線到伺服器",
+ "Disconnected": "連線已中斷",
+ "New connection has been rejected with reason: ": "連線被拒絕,原因:",
+ "New connection has been rejected": "連線被拒絕",
+ "Password is required": "請提供密碼",
+ "noVNC encountered an error:": "noVNC 遇到一個錯誤:",
+ "Hide/Show the control bar": "顯示/隱藏控制列",
+ "Move/Drag Viewport": "拖放顯示範圍",
+ "viewport drag": "顯示範圍拖放",
+ "Active Mouse Button": "啟用滑鼠按鍵",
+ "No mousebutton": "無滑鼠按鍵",
+ "Left mousebutton": "滑鼠左鍵",
+ "Middle mousebutton": "滑鼠中鍵",
+ "Right mousebutton": "滑鼠右鍵",
+ "Keyboard": "鍵盤",
+ "Show Keyboard": "顯示鍵盤",
+ "Extra keys": "額外按鍵",
+ "Show Extra Keys": "顯示額外按鍵",
+ "Ctrl": "Ctrl",
+ "Toggle Ctrl": "切換 Ctrl",
+ "Alt": "Alt",
+ "Toggle Alt": "切換 Alt",
+ "Send Tab": "送出 Tab 鍵",
+ "Tab": "Tab",
+ "Esc": "Esc",
+ "Send Escape": "送出 Escape 鍵",
+ "Ctrl+Alt+Del": "Ctrl-Alt-Del",
+ "Send Ctrl-Alt-Del": "送出 Ctrl-Alt-Del 快捷鍵",
+ "Shutdown/Reboot": "關機/重新啟動",
+ "Shutdown/Reboot...": "關機/重新啟動...",
+ "Power": "電源",
+ "Shutdown": "關機",
+ "Reboot": "重新啟動",
+ "Reset": "重設",
+ "Clipboard": "剪貼簿",
+ "Clear": "清除",
+ "Fullscreen": "全螢幕",
+ "Settings": "設定",
+ "Shared Mode": "分享模式",
+ "View Only": "僅檢視",
+ "Clip to Window": "限制/裁切視窗大小",
+ "Scaling Mode:": "縮放模式:",
+ "None": "無",
+ "Local Scaling": "本機縮放",
+ "Remote Resizing": "遠端調整大小",
+ "Advanced": "進階",
+ "Repeater ID:": "中繼站 ID",
+ "WebSocket": "WebSocket",
+ "Encrypt": "加密",
+ "Host:": "主機:",
+ "Port:": "連接埠:",
+ "Path:": "路徑:",
+ "Automatic Reconnect": "自動重新連線",
+ "Reconnect Delay (ms):": "重新連線間隔 (ms):",
+ "Logging:": "日誌級別:",
+ "Disconnect": "中斷連線",
+ "Connect": "連線",
+ "Password:": "密碼:",
+ "Cancel": "取消"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/localization.js b/systemvm/agent/noVNC/app/localization.js
new file mode 100644
index 0000000..100901c
--- /dev/null
+++ b/systemvm/agent/noVNC/app/localization.js
@@ -0,0 +1,172 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Localization Utilities
+ */
+
+export class Localizer {
+ constructor() {
+ // Currently configured language
+ this.language = 'en';
+
+ // Current dictionary of translations
+ this.dictionary = undefined;
+ }
+
+ // Configure suitable language based on user preferences
+ setup(supportedLanguages) {
+ this.language = 'en'; // Default: US English
+
+ /*
+ * Navigator.languages only available in Chrome (32+) and FireFox (32+)
+ * Fall back to navigator.language for other browsers
+ */
+ let userLanguages;
+ if (typeof window.navigator.languages == 'object') {
+ userLanguages = window.navigator.languages;
+ } else {
+ userLanguages = [navigator.language || navigator.userLanguage];
+ }
+
+ for (let i = 0;i < userLanguages.length;i++) {
+ const userLang = userLanguages[i]
+ .toLowerCase()
+ .replace("_", "-")
+ .split("-");
+
+ // Built-in default?
+ if ((userLang[0] === 'en') &&
+ ((userLang[1] === undefined) || (userLang[1] === 'us'))) {
+ return;
+ }
+
+ // First pass: perfect match
+ for (let j = 0; j < supportedLanguages.length; j++) {
+ const supLang = supportedLanguages[j]
+ .toLowerCase()
+ .replace("_", "-")
+ .split("-");
+
+ if (userLang[0] !== supLang[0]) {
+ continue;
+ }
+ if (userLang[1] !== supLang[1]) {
+ continue;
+ }
+
+ this.language = supportedLanguages[j];
+ return;
+ }
+
+ // Second pass: fallback
+ for (let j = 0;j < supportedLanguages.length;j++) {
+ const supLang = supportedLanguages[j]
+ .toLowerCase()
+ .replace("_", "-")
+ .split("-");
+
+ if (userLang[0] !== supLang[0]) {
+ continue;
+ }
+ if (supLang[1] !== undefined) {
+ continue;
+ }
+
+ this.language = supportedLanguages[j];
+ return;
+ }
+ }
+ }
+
+ // Retrieve localised text
+ get(id) {
+ if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
+ return this.dictionary[id];
+ } else {
+ return id;
+ }
+ }
+
+ // Traverses the DOM and translates relevant fields
+ // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
+ translateDOM() {
+ const self = this;
+
+ function process(elem, enabled) {
+ function isAnyOf(searchElement, items) {
+ return items.indexOf(searchElement) !== -1;
+ }
+
+ function translateAttribute(elem, attr) {
+ const str = self.get(elem.getAttribute(attr));
+ elem.setAttribute(attr, str);
+ }
+
+ function translateTextNode(node) {
+ const str = self.get(node.data.trim());
+ node.data = str;
+ }
+
+ if (elem.hasAttribute("translate")) {
+ if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
+ enabled = true;
+ } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
+ enabled = false;
+ }
+ }
+
+ if (enabled) {
+ if (elem.hasAttribute("abbr") &&
+ elem.tagName === "TH") {
+ translateAttribute(elem, "abbr");
+ }
+ if (elem.hasAttribute("alt") &&
+ isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
+ translateAttribute(elem, "alt");
+ }
+ if (elem.hasAttribute("download") &&
+ isAnyOf(elem.tagName, ["A", "AREA"])) {
+ translateAttribute(elem, "download");
+ }
+ if (elem.hasAttribute("label") &&
+ isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
+ "OPTION", "TRACK"])) {
+ translateAttribute(elem, "label");
+ }
+ // FIXME: Should update "lang"
+ if (elem.hasAttribute("placeholder") &&
+ isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
+ translateAttribute(elem, "placeholder");
+ }
+ if (elem.hasAttribute("title")) {
+ translateAttribute(elem, "title");
+ }
+ if (elem.hasAttribute("value") &&
+ elem.tagName === "INPUT" &&
+ isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) {
+ translateAttribute(elem, "value");
+ }
+ }
+
+ for (let i = 0; i < elem.childNodes.length; i++) {
+ const node = elem.childNodes[i];
+ if (node.nodeType === node.ELEMENT_NODE) {
+ process(node, enabled);
+ } else if (node.nodeType === node.TEXT_NODE && enabled) {
+ translateTextNode(node);
+ }
+ }
+ }
+
+ process(document.body, true);
+ }
+}
+
+export const l10n = new Localizer();
+export default l10n.get.bind(l10n);
diff --git a/systemvm/agent/noVNC/app/sounds/CREDITS b/systemvm/agent/noVNC/app/sounds/CREDITS
new file mode 100644
index 0000000..ec1fb55
--- /dev/null
+++ b/systemvm/agent/noVNC/app/sounds/CREDITS
@@ -0,0 +1,4 @@
+bell
+ Copyright: Dr. Richard Boulanger et al
+ URL: http://www.archive.org/details/Berklee44v12
+ License: CC-BY Attribution 3.0 Unported
diff --git a/systemvm/agent/noVNC/app/sounds/bell.mp3 b/systemvm/agent/noVNC/app/sounds/bell.mp3
new file mode 100644
index 0000000..fdbf149
--- /dev/null
+++ b/systemvm/agent/noVNC/app/sounds/bell.mp3
Binary files differ
diff --git a/systemvm/agent/noVNC/app/sounds/bell.oga b/systemvm/agent/noVNC/app/sounds/bell.oga
new file mode 100644
index 0000000..144d2b3
--- /dev/null
+++ b/systemvm/agent/noVNC/app/sounds/bell.oga
Binary files differ
diff --git a/systemvm/agent/noVNC/app/styles/Orbitron700.ttf b/systemvm/agent/noVNC/app/styles/Orbitron700.ttf
new file mode 100644
index 0000000..e28729d
--- /dev/null
+++ b/systemvm/agent/noVNC/app/styles/Orbitron700.ttf
Binary files differ
diff --git a/systemvm/agent/noVNC/app/styles/Orbitron700.woff b/systemvm/agent/noVNC/app/styles/Orbitron700.woff
new file mode 100644
index 0000000..61db630
--- /dev/null
+++ b/systemvm/agent/noVNC/app/styles/Orbitron700.woff
Binary files differ
diff --git a/systemvm/agent/noVNC/app/styles/base.css b/systemvm/agent/noVNC/app/styles/base.css
new file mode 100644
index 0000000..3ca9894
--- /dev/null
+++ b/systemvm/agent/noVNC/app/styles/base.css
@@ -0,0 +1,900 @@
+/*
+ * noVNC base CSS
+ * Copyright (C) 2018 The noVNC Authors
+ * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+ */
+
+/*
+ * Z index layers:
+ *
+ * 0: Main screen
+ * 10: Control bar
+ * 50: Transition blocker
+ * 60: Connection popups
+ * 100: Status bar
+ * ...
+ * 1000: Javascript crash
+ * ...
+ * 10000: Max (used for polyfills)
+ */
+
+body {
+ margin:0;
+ padding:0;
+ font-family: Helvetica;
+ /*Background image with light grey curve.*/
+ background-color:#494949;
+ background-repeat:no-repeat;
+ background-position:right bottom;
+ height:100%;
+ touch-action: none;
+}
+
+html {
+ height:100%;
+}
+
+.noVNC_only_touch.noVNC_hidden {
+ display: none;
+}
+
+.noVNC_disabled {
+ color: rgb(128, 128, 128);
+}
+
+/* ----------------------------------------
+ * Spinner
+ * ----------------------------------------
+ */
+
+.noVNC_spinner {
+ position: relative;
+}
+.noVNC_spinner, .noVNC_spinner::before, .noVNC_spinner::after {
+ width: 10px;
+ height: 10px;
+ border-radius: 2px;
+ box-shadow: -60px 10px 0 rgba(255, 255, 255, 0);
+ animation: noVNC_spinner 1.0s linear infinite;
+}
+.noVNC_spinner::before {
+ content: "";
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ animation-delay: -0.1s;
+}
+.noVNC_spinner::after {
+ content: "";
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ animation-delay: 0.1s;
+}
+@keyframes noVNC_spinner {
+ 0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; }
+ 25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; }
+ 50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; }
+}
+
+/* ----------------------------------------
+ * Input Elements
+ * ----------------------------------------
+ */
+
+input[type=input], input[type=password], input[type=number],
+input:not([type]), textarea {
+ /* Disable default rendering */
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background: none;
+
+ margin: 2px;
+ padding: 2px;
+ border: 1px solid rgb(192, 192, 192);
+ border-radius: 5px;
+ color: black;
+ background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
+}
+
+input[type=button], input[type=submit], select {
+ /* Disable default rendering */
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background: none;
+
+ margin: 2px;
+ padding: 2px;
+ border: 1px solid rgb(192, 192, 192);
+ border-bottom-width: 2px;
+ border-radius: 5px;
+ color: black;
+ background: linear-gradient(to top, rgb(255, 255, 255), rgb(240, 240, 240));
+
+ /* This avoids it jumping around when :active */
+ vertical-align: middle;
+}
+
+input[type=button], input[type=submit] {
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+option {
+ color: black;
+ background: white;
+}
+
+input[type=input]:focus, input[type=password]:focus,
+input:not([type]):focus, input[type=button]:focus,
+input[type=submit]:focus,
+textarea:focus, select:focus {
+ box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
+ border-color: rgb(74, 144, 217);
+ outline: none;
+}
+
+input[type=button]::-moz-focus-inner,
+input[type=submit]::-moz-focus-inner {
+ border: none;
+}
+
+input[type=input]:disabled, input[type=password]:disabled,
+input:not([type]):disabled, input[type=button]:disabled,
+input[type=submit]:disabled, input[type=number]:disabled,
+textarea:disabled, select:disabled {
+ color: rgb(128, 128, 128);
+ background: rgb(240, 240, 240);
+}
+
+input[type=button]:active, input[type=submit]:active,
+select:active {
+ border-bottom-width: 1px;
+ margin-top: 3px;
+}
+
+:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
+:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
+:root:not(.noVNC_touch) select:hover:not(:disabled) {
+ background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
+}
+
+/* ----------------------------------------
+ * WebKit centering hacks
+ * ----------------------------------------
+ */
+
+.noVNC_center {
+ /*
+ * This is a workaround because webkit misrenders transforms and
+ * uses non-integer coordinates, resulting in blurry content.
+ * Ideally we'd use "top: 50%; transform: translateY(-50%);" on
+ * the objects instead.
+ */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+.noVNC_center > * {
+ pointer-events: auto;
+}
+.noVNC_vcenter {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ pointer-events: none;
+}
+.noVNC_vcenter > * {
+ pointer-events: auto;
+}
+
+/* ----------------------------------------
+ * Layering
+ * ----------------------------------------
+ */
+
+.noVNC_connect_layer {
+ z-index: 60;
+}
+
+/* ----------------------------------------
+ * Fallback error
+ * ----------------------------------------
+ */
+
+#noVNC_fallback_error {
+ z-index: 1000;
+ visibility: hidden;
+}
+#noVNC_fallback_error.noVNC_open {
+ visibility: visible;
+}
+
+#noVNC_fallback_error > div {
+ max-width: 90%;
+ padding: 15px;
+
+ transition: 0.5s ease-in-out;
+
+ transform: translateY(-50px);
+ opacity: 0;
+
+ text-align: center;
+ font-weight: bold;
+ color: #fff;
+
+ border-radius: 10px;
+ box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+ background: rgba(200,55,55,0.8);
+}
+#noVNC_fallback_error.noVNC_open > div {
+ transform: translateY(0);
+ opacity: 1;
+}
+
+#noVNC_fallback_errormsg {
+ font-weight: normal;
+}
+
+#noVNC_fallback_errormsg .noVNC_message {
+ display: inline-block;
+ text-align: left;
+ font-family: monospace;
+ white-space: pre-wrap;
+}
+
+#noVNC_fallback_error .noVNC_location {
+ font-style: italic;
+ font-size: 0.8em;
+ color: rgba(255, 255, 255, 0.8);
+}
+
+#noVNC_fallback_error .noVNC_stack {
+ max-height: 50vh;
+ padding: 10px;
+ margin: 10px;
+ font-size: 0.8em;
+ text-align: left;
+ font-family: monospace;
+ white-space: pre;
+ border: 1px solid rgba(0, 0, 0, 0.5);
+ background: rgba(0, 0, 0, 0.2);
+ overflow: auto;
+}
+
+/* ----------------------------------------
+ * Control Bar
+ * ----------------------------------------
+ */
+
+#noVNC_control_bar_anchor {
+ /* The anchor is needed to get z-stacking to work */
+ position: fixed;
+ z-index: 10;
+
+ transition: 0.5s ease-in-out;
+
+ /* Edge misrenders animations wihthout this */
+ transform: translateX(0);
+}
+:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {
+ opacity: 0.8;
+}
+#noVNC_control_bar_anchor.noVNC_right {
+ left: auto;
+ right: 0;
+}
+
+#noVNC_control_bar {
+ position: relative;
+ left: -100%;
+
+ transition: 0.5s ease-in-out;
+
+ background-color: rgb(110, 132, 163);
+ border-radius: 0 10px 10px 0;
+
+}
+#noVNC_control_bar.noVNC_open {
+ box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+ left: 0;
+}
+#noVNC_control_bar::before {
+ /* This extra element is to get a proper shadow */
+ content: "";
+ position: absolute;
+ z-index: -1;
+ height: 100%;
+ width: 30px;
+ left: -30px;
+ transition: box-shadow 0.5s ease-in-out;
+}
+#noVNC_control_bar.noVNC_open::before {
+ box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+}
+.noVNC_right #noVNC_control_bar {
+ left: 100%;
+ border-radius: 10px 0 0 10px;
+}
+.noVNC_right #noVNC_control_bar.noVNC_open {
+ left: 0;
+}
+.noVNC_right #noVNC_control_bar::before {
+ visibility: hidden;
+}
+
+#noVNC_control_bar_handle {
+ position: absolute;
+ left: -15px;
+ top: 0;
+ transform: translateY(35px);
+ width: calc(100% + 30px);
+ height: 50px;
+ z-index: -1;
+ cursor: pointer;
+ border-radius: 5px;
+ background-color: rgb(83, 99, 122);
+ background-image: url("../images/handle_bg.svg");
+ background-repeat: no-repeat;
+ background-position: right;
+ box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
+}
+#noVNC_control_bar_handle:after {
+ content: "";
+ transition: transform 0.5s ease-in-out;
+ background: url("../images/handle.svg");
+ position: absolute;
+ top: 22px; /* (50px-6px)/2 */
+ right: 5px;
+ width: 5px;
+ height: 6px;
+}
+#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
+ transform: translateX(1px) rotate(180deg);
+}
+:root:not(.noVNC_connected) #noVNC_control_bar_handle {
+ display: none;
+}
+.noVNC_right #noVNC_control_bar_handle {
+ background-position: left;
+}
+.noVNC_right #noVNC_control_bar_handle:after {
+ left: 5px;
+ right: 0;
+ transform: translateX(1px) rotate(180deg);
+}
+.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
+ transform: none;
+}
+#noVNC_control_bar_handle div {
+ position: absolute;
+ right: -35px;
+ top: 0;
+ width: 50px;
+ height: 50px;
+}
+:root:not(.noVNC_touch) #noVNC_control_bar_handle div {
+ display: none;
+}
+.noVNC_right #noVNC_control_bar_handle div {
+ left: -35px;
+ right: auto;
+}
+
+#noVNC_control_bar .noVNC_scroll {
+ max-height: 100vh; /* Chrome is buggy with 100% */
+ overflow-x: hidden;
+ overflow-y: auto;
+ padding: 0 10px 0 5px;
+}
+.noVNC_right #noVNC_control_bar .noVNC_scroll {
+ padding: 0 5px 0 10px;
+}
+
+/* Control bar hint */
+#noVNC_control_bar_hint {
+ position: fixed;
+ left: calc(100vw - 50px);
+ right: auto;
+ top: 50%;
+ transform: translateY(-50%) scale(0);
+ width: 100px;
+ height: 50%;
+ max-height: 600px;
+
+ visibility: hidden;
+ opacity: 0;
+ transition: 0.2s ease-in-out;
+ background: transparent;
+ box-shadow: 0 0 10px black, inset 0 0 10px 10px rgba(110, 132, 163, 0.8);
+ border-radius: 10px;
+ transition-delay: 0s;
+}
+#noVNC_control_bar_anchor.noVNC_right #noVNC_control_bar_hint{
+ left: auto;
+ right: calc(100vw - 50px);
+}
+#noVNC_control_bar_hint.noVNC_active {
+ visibility: visible;
+ opacity: 1;
+ transition-delay: 0.2s;
+ transform: translateY(-50%) scale(1);
+}
+
+/* General button style */
+.noVNC_button {
+ display: block;
+ padding: 4px 4px;
+ margin: 10px 0;
+ vertical-align: middle;
+ border:1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 6px;
+}
+.noVNC_button.noVNC_selected {
+ border-color: rgba(0, 0, 0, 0.8);
+ background: rgba(0, 0, 0, 0.5);
+}
+.noVNC_button:disabled {
+ opacity: 0.4;
+}
+.noVNC_button:focus {
+ outline: none;
+}
+.noVNC_button:active {
+ padding-top: 5px;
+ padding-bottom: 3px;
+}
+/* Android browsers don't properly update hover state if touch events
+ * are intercepted, but focus should be safe to display */
+:root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover,
+.noVNC_button.noVNC_selected:focus {
+ border-color: rgba(0, 0, 0, 0.4);
+ background: rgba(0, 0, 0, 0.2);
+}
+:root:not(.noVNC_touch) .noVNC_button:hover,
+.noVNC_button:focus {
+ background: rgba(255, 255, 255, 0.2);
+}
+.noVNC_button.noVNC_hidden {
+ display: none;
+}
+
+/* Panels */
+.noVNC_panel {
+ transform: translateX(25px);
+
+ transition: 0.5s ease-in-out;
+
+ max-height: 100vh; /* Chrome is buggy with 100% */
+ overflow-x: hidden;
+ overflow-y: auto;
+
+ visibility: hidden;
+ opacity: 0;
+
+ padding: 15px;
+
+ background: #fff;
+ border-radius: 10px;
+ color: #000;
+ border: 2px solid #E0E0E0;
+ box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+}
+.noVNC_panel.noVNC_open {
+ visibility: visible;
+ opacity: 1;
+ transform: translateX(75px);
+}
+.noVNC_right .noVNC_vcenter {
+ left: auto;
+ right: 0;
+}
+.noVNC_right .noVNC_panel {
+ transform: translateX(-25px);
+}
+.noVNC_right .noVNC_panel.noVNC_open {
+ transform: translateX(-75px);
+}
+
+.noVNC_panel hr {
+ border: none;
+ border-top: 1px solid rgb(192, 192, 192);
+}
+
+.noVNC_panel label {
+ display: block;
+ white-space: nowrap;
+}
+
+.noVNC_panel .noVNC_heading {
+ background-color: rgb(110, 132, 163);
+ border-radius: 5px;
+ padding: 5px;
+ /* Compensate for padding in image */
+ padding-right: 8px;
+ color: white;
+ font-size: 20px;
+ margin-bottom: 10px;
+ white-space: nowrap;
+}
+.noVNC_panel .noVNC_heading img {
+ vertical-align: bottom;
+}
+
+.noVNC_submit {
+ float: right;
+}
+
+/* Expanders */
+.noVNC_expander {
+ cursor: pointer;
+}
+.noVNC_expander::before {
+ content: url("../images/expander.svg");
+ display: inline-block;
+ margin-right: 5px;
+ transition: 0.2s ease-in-out;
+}
+.noVNC_expander.noVNC_open::before {
+ transform: rotateZ(90deg);
+}
+.noVNC_expander ~ * {
+ margin: 5px;
+ margin-left: 10px;
+ padding: 5px;
+ background: rgba(0, 0, 0, 0.05);
+ border-radius: 5px;
+}
+.noVNC_expander:not(.noVNC_open) ~ * {
+ display: none;
+}
+
+/* Control bar content */
+
+#noVNC_control_bar .noVNC_logo {
+ font-size: 13px;
+}
+
+:root:not(.noVNC_connected) #noVNC_view_drag_button {
+ display: none;
+}
+
+/* noVNC Touch Device only buttons */
+:root:not(.noVNC_connected) #noVNC_mobile_buttons {
+ display: none;
+}
+:root:not(.noVNC_touch) #noVNC_mobile_buttons {
+ display: none;
+}
+
+/* Extra manual keys */
+:root:not(.noVNC_connected) #noVNC_extra_keys {
+ display: none;
+}
+
+#noVNC_modifiers {
+ background-color: rgb(92, 92, 92);
+ border: none;
+ padding: 0 10px;
+}
+
+/* Shutdown/Reboot */
+:root:not(.noVNC_connected) #noVNC_power_button {
+ display: none;
+}
+#noVNC_power {
+}
+#noVNC_power_buttons {
+ display: none;
+}
+
+#noVNC_power input[type=button] {
+ width: 100%;
+}
+
+/* Clipboard */
+:root:not(.noVNC_connected) #noVNC_clipboard_button {
+ display: none;
+}
+#noVNC_clipboard {
+ /* Full screen, minus padding and left and right margins */
+ max-width: calc(100vw - 2*15px - 75px - 25px);
+}
+#noVNC_clipboard_text {
+ width: 500px;
+ max-width: 100%;
+}
+
+/* Settings */
+#noVNC_settings {
+}
+#noVNC_settings ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+#noVNC_setting_port {
+ width: 80px;
+}
+#noVNC_setting_path {
+ width: 100px;
+}
+
+/* Connection Controls */
+:root:not(.noVNC_connected) #noVNC_disconnect_button {
+ display: none;
+}
+
+/* ----------------------------------------
+ * Status Dialog
+ * ----------------------------------------
+ */
+
+#noVNC_status {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ z-index: 100;
+ transform: translateY(-100%);
+
+ cursor: pointer;
+
+ transition: 0.5s ease-in-out;
+
+ visibility: hidden;
+ opacity: 0;
+
+ padding: 5px;
+
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-content: center;
+
+ line-height: 25px;
+ word-wrap: break-word;
+ color: #fff;
+
+ border-bottom: 1px solid rgba(0, 0, 0, 0.9);
+}
+#noVNC_status.noVNC_open {
+ transform: translateY(0);
+ visibility: visible;
+ opacity: 1;
+}
+
+#noVNC_status::before {
+ content: "";
+ display: inline-block;
+ width: 25px;
+ height: 25px;
+ margin-right: 5px;
+}
+
+#noVNC_status.noVNC_status_normal {
+ background: rgba(128,128,128,0.9);
+}
+#noVNC_status.noVNC_status_normal::before {
+ content: url("../images/info.svg") " ";
+}
+#noVNC_status.noVNC_status_error {
+ background: rgba(200,55,55,0.9);
+}
+#noVNC_status.noVNC_status_error::before {
+ content: url("../images/error.svg") " ";
+}
+#noVNC_status.noVNC_status_warn {
+ background: rgba(180,180,30,0.9);
+}
+#noVNC_status.noVNC_status_warn::before {
+ content: url("../images/warning.svg") " ";
+}
+
+/* ----------------------------------------
+ * Connect Dialog
+ * ----------------------------------------
+ */
+
+#noVNC_connect_dlg {
+ transition: 0.5s ease-in-out;
+
+ transform: scale(0, 0);
+ visibility: hidden;
+ opacity: 0;
+}
+#noVNC_connect_dlg.noVNC_open {
+ transform: scale(1, 1);
+ visibility: visible;
+ opacity: 1;
+}
+#noVNC_connect_dlg .noVNC_logo {
+ transition: 0.5s ease-in-out;
+ padding: 10px;
+ margin-bottom: 10px;
+
+ font-size: 80px;
+ text-align: center;
+
+ border-radius: 5px;
+}
+@media (max-width: 440px) {
+ #noVNC_connect_dlg {
+ max-width: calc(100vw - 100px);
+ }
+ #noVNC_connect_dlg .noVNC_logo {
+ font-size: calc(25vw - 30px);
+ }
+}
+#noVNC_connect_button {
+ cursor: pointer;
+
+ padding: 10px;
+
+ color: white;
+ background-color: rgb(110, 132, 163);
+ border-radius: 12px;
+
+ text-align: center;
+ font-size: 20px;
+
+ box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+}
+#noVNC_connect_button div {
+ margin: 2px;
+ padding: 5px 30px;
+ border: 1px solid rgb(83, 99, 122);
+ border-bottom-width: 2px;
+ border-radius: 5px;
+ background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147));
+
+ /* This avoids it jumping around when :active */
+ vertical-align: middle;
+}
+#noVNC_connect_button div:active {
+ border-bottom-width: 1px;
+ margin-top: 3px;
+}
+:root:not(.noVNC_touch) #noVNC_connect_button div:hover {
+ background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155));
+}
+
+#noVNC_connect_button img {
+ vertical-align: bottom;
+ height: 1.3em;
+}
+
+/* ----------------------------------------
+ * Password Dialog
+ * ----------------------------------------
+ */
+
+#noVNC_password_dlg {
+ position: relative;
+
+ transform: translateY(-50px);
+}
+#noVNC_password_dlg.noVNC_open {
+ transform: translateY(0);
+}
+#noVNC_password_dlg ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+
+/* ----------------------------------------
+ * Main Area
+ * ----------------------------------------
+ */
+
+/* Transition screen */
+#noVNC_transition {
+ display: none;
+
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+
+ color: white;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 50;
+
+ /*display: flex;*/
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+:root.noVNC_loading #noVNC_transition,
+:root.noVNC_connecting #noVNC_transition,
+:root.noVNC_disconnecting #noVNC_transition,
+:root.noVNC_reconnecting #noVNC_transition {
+ display: flex;
+}
+:root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button {
+ display: none;
+}
+#noVNC_transition_text {
+ font-size: 1.5em;
+}
+
+/* Main container */
+#noVNC_container {
+ width: 100%;
+ height: 100%;
+ background-color: #313131;
+ border-bottom-right-radius: 800px 600px;
+ /*border-top-left-radius: 800px 600px;*/
+}
+
+#noVNC_keyboardinput {
+ width: 1px;
+ height: 1px;
+ background-color: #fff;
+ color: #fff;
+ border: 0;
+ position: absolute;
+ left: -40px;
+ z-index: -1;
+ ime-mode: disabled;
+}
+
+/*Default noVNC logo.*/
+/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
+@font-face {
+ font-family: 'Orbitron';
+ font-style: normal;
+ font-weight: 700;
+ src: local('?'), url('Orbitron700.woff') format('woff'),
+ url('Orbitron700.ttf') format('truetype');
+}
+
+.noVNC_logo {
+ color:yellow;
+ font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
+ line-height:90%;
+ text-shadow: 0.1em 0.1em 0 black;
+}
+.noVNC_logo span{
+ color:green;
+}
+
+#noVNC_bell {
+ display: none;
+}
+
+/* ----------------------------------------
+ * Media sizing
+ * ----------------------------------------
+ */
+
+@media screen and (max-width: 640px){
+ #noVNC_logo {
+ font-size: 150px;
+ }
+}
+
+@media screen and (min-width: 321px) and (max-width: 480px) {
+ #noVNC_logo {
+ font-size: 110px;
+ }
+}
+
+@media screen and (max-width: 320px) {
+ #noVNC_logo {
+ font-size: 90px;
+ }
+}
diff --git a/systemvm/agent/noVNC/app/ui.js b/systemvm/agent/noVNC/app/ui.js
new file mode 100644
index 0000000..13d1c01
--- /dev/null
+++ b/systemvm/agent/noVNC/app/ui.js
@@ -0,0 +1,1660 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+import * as Log from '../core/util/logging.js';
+import _, { l10n } from './localization.js';
+import { isTouchDevice, isSafari, isIOS, isAndroid, dragThreshold }
+ from '../core/util/browser.js';
+import { setCapture, getPointerEvent } from '../core/util/events.js';
+import KeyTable from "../core/input/keysym.js";
+import keysyms from "../core/input/keysymdef.js";
+import Keyboard from "../core/input/keyboard.js";
+import RFB from "../core/rfb.js";
+import * as WebUtil from "./webutil.js";
+
+const UI = {
+
+ connected: false,
+ desktopName: "",
+
+ statusTimeout: null,
+ hideKeyboardTimeout: null,
+ idleControlbarTimeout: null,
+ closeControlbarTimeout: null,
+
+ controlbarGrabbed: false,
+ controlbarDrag: false,
+ controlbarMouseDownClientY: 0,
+ controlbarMouseDownOffsetY: 0,
+
+ lastKeyboardinput: null,
+ defaultKeyboardinputLen: 100,
+
+ inhibit_reconnect: true,
+ reconnect_callback: null,
+ reconnect_password: null,
+
+ prime() {
+ return WebUtil.initSettings().then(() => {
+ if (document.readyState === "interactive" || document.readyState === "complete") {
+ return UI.start();
+ }
+
+ return new Promise((resolve, reject) => {
+ document.addEventListener('DOMContentLoaded', () => UI.start().then(resolve).catch(reject));
+ });
+ });
+ },
+
+ // Render default UI and initialize settings menu
+ start() {
+
+ UI.initSettings();
+
+ // Translate the DOM
+ l10n.translateDOM();
+
+ // Adapt the interface for touch screen devices
+ if (isTouchDevice) {
+ document.documentElement.classList.add("noVNC_touch");
+ // Remove the address bar
+ setTimeout(() => window.scrollTo(0, 1), 100);
+ }
+
+ // Restore control bar position
+ if (WebUtil.readSetting('controlbar_pos') === 'right') {
+ UI.toggleControlbarSide();
+ }
+
+ UI.initFullscreen();
+
+ // Setup event handlers
+ UI.addControlbarHandlers();
+ UI.addTouchSpecificHandlers();
+ UI.addExtraKeysHandlers();
+ UI.addMachineHandlers();
+ UI.addConnectionControlHandlers();
+ UI.addClipboardHandlers();
+ UI.addSettingsHandlers();
+ document.getElementById("noVNC_status")
+ .addEventListener('click', UI.hideStatus);
+
+ // Bootstrap fallback input handler
+ UI.keyboardinputReset();
+
+ UI.openControlbar();
+
+ UI.updateVisualState('init');
+
+ document.documentElement.classList.remove("noVNC_loading");
+
+ let autoconnect = WebUtil.getConfigVar('autoconnect', false);
+ if (autoconnect === 'true' || autoconnect == '1') {
+ autoconnect = true;
+ UI.connect();
+ } else {
+ autoconnect = false;
+ // Show the connect panel on first load unless autoconnecting
+ UI.openConnectPanel();
+ }
+
+ return Promise.resolve(UI.rfb);
+ },
+
+ initFullscreen() {
+ // Only show the button if fullscreen is properly supported
+ // * Safari doesn't support alphanumerical input while in fullscreen
+ if (!isSafari() &&
+ (document.documentElement.requestFullscreen ||
+ document.documentElement.mozRequestFullScreen ||
+ document.documentElement.webkitRequestFullscreen ||
+ document.body.msRequestFullscreen)) {
+ document.getElementById('noVNC_fullscreen_button')
+ .classList.remove("noVNC_hidden");
+ UI.addFullscreenHandlers();
+ }
+ },
+
+ initSettings() {
+ // Logging selection dropdown
+ const llevels = ['error', 'warn', 'info', 'debug'];
+ for (let i = 0; i < llevels.length; i += 1) {
+ UI.addOption(document.getElementById('noVNC_setting_logging'), llevels[i], llevels[i]);
+ }
+
+ // Settings with immediate effects
+ UI.initSetting('logging', 'warn');
+ UI.updateLogging();
+
+ // if port == 80 (or 443) then it won't be present and should be
+ // set manually
+ let port = window.location.port;
+ if (!port) {
+ if (window.location.protocol.substring(0, 5) == 'https') {
+ port = 443;
+ } else if (window.location.protocol.substring(0, 4) == 'http') {
+ port = 80;
+ }
+ }
+
+ /* Populate the controls if defaults are provided in the URL */
+ UI.initSetting('host', window.location.hostname);
+ UI.initSetting('port', port);
+ UI.initSetting('encrypt', (window.location.protocol === "https:"));
+ UI.initSetting('view_clip', false);
+ UI.initSetting('resize', 'off');
+ UI.initSetting('shared', false);
+ UI.initSetting('view_only', false);
+ UI.initSetting('show_dot', false);
+ UI.initSetting('path', 'websockify');
+ UI.initSetting('repeaterID', '');
+ UI.initSetting('reconnect', false);
+ UI.initSetting('reconnect_delay', 5000);
+
+ UI.setupSettingLabels();
+ },
+ // Adds a link to the label elements on the corresponding input elements
+ setupSettingLabels() {
+ const labels = document.getElementsByTagName('LABEL');
+ for (let i = 0; i < labels.length; i++) {
+ const htmlFor = labels[i].htmlFor;
+ if (htmlFor != '') {
+ const elem = document.getElementById(htmlFor);
+ if (elem) elem.label = labels[i];
+ } else {
+ // If 'for' isn't set, use the first input element child
+ const children = labels[i].children;
+ for (let j = 0; j < children.length; j++) {
+ if (children[j].form !== undefined) {
+ children[j].label = labels[i];
+ break;
+ }
+ }
+ }
+ }
+ },
+
+/* ------^-------
+* /INIT
+* ==============
+* EVENT HANDLERS
+* ------v------*/
+
+ addControlbarHandlers() {
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('mousemove', UI.activateControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('mouseup', UI.activateControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('mousedown', UI.activateControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('keydown', UI.activateControlbar);
+
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('mousedown', UI.keepControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('keydown', UI.keepControlbar);
+
+ document.getElementById("noVNC_view_drag_button")
+ .addEventListener('click', UI.toggleViewDrag);
+
+ document.getElementById("noVNC_control_bar_handle")
+ .addEventListener('mousedown', UI.controlbarHandleMouseDown);
+ document.getElementById("noVNC_control_bar_handle")
+ .addEventListener('mouseup', UI.controlbarHandleMouseUp);
+ document.getElementById("noVNC_control_bar_handle")
+ .addEventListener('mousemove', UI.dragControlbarHandle);
+ // resize events aren't available for elements
+ window.addEventListener('resize', UI.updateControlbarHandle);
+
+ const exps = document.getElementsByClassName("noVNC_expander");
+ for (let i = 0;i < exps.length;i++) {
+ exps[i].addEventListener('click', UI.toggleExpander);
+ }
+ },
+
+ addTouchSpecificHandlers() {
+ document.getElementById("noVNC_mouse_button0")
+ .addEventListener('click', () => UI.setMouseButton(1));
+ document.getElementById("noVNC_mouse_button1")
+ .addEventListener('click', () => UI.setMouseButton(2));
+ document.getElementById("noVNC_mouse_button2")
+ .addEventListener('click', () => UI.setMouseButton(4));
+ document.getElementById("noVNC_mouse_button4")
+ .addEventListener('click', () => UI.setMouseButton(0));
+ document.getElementById("noVNC_keyboard_button")
+ .addEventListener('click', UI.toggleVirtualKeyboard);
+
+ UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));
+ UI.touchKeyboard.onkeyevent = UI.keyEvent;
+ UI.touchKeyboard.grab();
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('input', UI.keyInput);
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('focus', UI.onfocusVirtualKeyboard);
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('blur', UI.onblurVirtualKeyboard);
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('submit', () => false);
+
+ document.documentElement
+ .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
+
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('touchstart', UI.activateControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('touchmove', UI.activateControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('touchend', UI.activateControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('input', UI.activateControlbar);
+
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('touchstart', UI.keepControlbar);
+ document.getElementById("noVNC_control_bar")
+ .addEventListener('input', UI.keepControlbar);
+
+ document.getElementById("noVNC_control_bar_handle")
+ .addEventListener('touchstart', UI.controlbarHandleMouseDown);
+ document.getElementById("noVNC_control_bar_handle")
+ .addEventListener('touchend', UI.controlbarHandleMouseUp);
+ document.getElementById("noVNC_control_bar_handle")
+ .addEventListener('touchmove', UI.dragControlbarHandle);
+ },
+
+ addExtraKeysHandlers() {
+ document.getElementById("noVNC_toggle_extra_keys_button")
+ .addEventListener('click', UI.toggleExtraKeys);
+ document.getElementById("noVNC_toggle_ctrl_button")
+ .addEventListener('click', UI.toggleCtrl);
+ document.getElementById("noVNC_toggle_windows_button")
+ .addEventListener('click', UI.toggleWindows);
+ document.getElementById("noVNC_toggle_alt_button")
+ .addEventListener('click', UI.toggleAlt);
+ document.getElementById("noVNC_send_tab_button")
+ .addEventListener('click', UI.sendTab);
+ document.getElementById("noVNC_send_esc_button")
+ .addEventListener('click', UI.sendEsc);
+ document.getElementById("noVNC_send_ctrl_alt_del_button")
+ .addEventListener('click', UI.sendCtrlAltDel);
+ },
+
+ addMachineHandlers() {
+ document.getElementById("noVNC_shutdown_button")
+ .addEventListener('click', () => UI.rfb.machineShutdown());
+ document.getElementById("noVNC_reboot_button")
+ .addEventListener('click', () => UI.rfb.machineReboot());
+ document.getElementById("noVNC_reset_button")
+ .addEventListener('click', () => UI.rfb.machineReset());
+ document.getElementById("noVNC_power_button")
+ .addEventListener('click', UI.togglePowerPanel);
+ },
+
+ addConnectionControlHandlers() {
+ document.getElementById("noVNC_disconnect_button")
+ .addEventListener('click', UI.disconnect);
+ document.getElementById("noVNC_connect_button")
+ .addEventListener('click', UI.connect);
+ document.getElementById("noVNC_cancel_reconnect_button")
+ .addEventListener('click', UI.cancelReconnect);
+
+ document.getElementById("noVNC_password_button")
+ .addEventListener('click', UI.setPassword);
+ },
+
+ addClipboardHandlers() {
+ document.getElementById("noVNC_clipboard_button")
+ .addEventListener('click', UI.toggleClipboardPanel);
+ document.getElementById("noVNC_clipboard_text")
+ .addEventListener('change', UI.clipboardSend);
+ document.getElementById("noVNC_clipboard_clear_button")
+ .addEventListener('click', UI.clipboardClear);
+ },
+
+ // Add a call to save settings when the element changes,
+ // unless the optional parameter changeFunc is used instead.
+ addSettingChangeHandler(name, changeFunc) {
+ const settingElem = document.getElementById("noVNC_setting_" + name);
+ if (changeFunc === undefined) {
+ changeFunc = () => UI.saveSetting(name);
+ }
+ settingElem.addEventListener('change', changeFunc);
+ },
+
+ addSettingsHandlers() {
+ document.getElementById("noVNC_settings_button")
+ .addEventListener('click', UI.toggleSettingsPanel);
+
+ UI.addSettingChangeHandler('encrypt');
+ UI.addSettingChangeHandler('resize');
+ UI.addSettingChangeHandler('resize', UI.applyResizeMode);
+ UI.addSettingChangeHandler('resize', UI.updateViewClip);
+ UI.addSettingChangeHandler('view_clip');
+ UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
+ UI.addSettingChangeHandler('shared');
+ UI.addSettingChangeHandler('view_only');
+ UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
+ UI.addSettingChangeHandler('show_dot');
+ UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor);
+ UI.addSettingChangeHandler('host');
+ UI.addSettingChangeHandler('port');
+ UI.addSettingChangeHandler('path');
+ UI.addSettingChangeHandler('repeaterID');
+ UI.addSettingChangeHandler('logging');
+ UI.addSettingChangeHandler('logging', UI.updateLogging);
+ UI.addSettingChangeHandler('reconnect');
+ UI.addSettingChangeHandler('reconnect_delay');
+ },
+
+ addFullscreenHandlers() {
+ document.getElementById("noVNC_fullscreen_button")
+ .addEventListener('click', UI.toggleFullscreen);
+
+ window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
+ window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
+ window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton);
+ window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
+ },
+
+/* ------^-------
+ * /EVENT HANDLERS
+ * ==============
+ * VISUAL
+ * ------v------*/
+
+ // Disable/enable controls depending on connection state
+ updateVisualState(state) {
+
+ document.documentElement.classList.remove("noVNC_connecting");
+ document.documentElement.classList.remove("noVNC_connected");
+ document.documentElement.classList.remove("noVNC_disconnecting");
+ document.documentElement.classList.remove("noVNC_reconnecting");
+
+ const transition_elem = document.getElementById("noVNC_transition_text");
+ switch (state) {
+ case 'init':
+ break;
+ case 'connecting':
+ transition_elem.textContent = _("Connecting...");
+ document.documentElement.classList.add("noVNC_connecting");
+ break;
+ case 'connected':
+ document.documentElement.classList.add("noVNC_connected");
+ break;
+ case 'disconnecting':
+ transition_elem.textContent = _("Disconnecting...");
+ document.documentElement.classList.add("noVNC_disconnecting");
+ break;
+ case 'disconnected':
+ break;
+ case 'reconnecting':
+ transition_elem.textContent = _("Reconnecting...");
+ document.documentElement.classList.add("noVNC_reconnecting");
+ break;
+ default:
+ Log.Error("Invalid visual state: " + state);
+ UI.showStatus(_("Internal error"), 'error');
+ return;
+ }
+
+ if (UI.connected) {
+ UI.updateViewClip();
+
+ UI.disableSetting('encrypt');
+ UI.disableSetting('shared');
+ UI.disableSetting('host');
+ UI.disableSetting('port');
+ UI.disableSetting('path');
+ UI.disableSetting('repeaterID');
+ UI.setMouseButton(1);
+
+ // Hide the controlbar after 2 seconds
+ UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
+ } else {
+ UI.enableSetting('encrypt');
+ UI.enableSetting('shared');
+ UI.enableSetting('host');
+ UI.enableSetting('port');
+ UI.enableSetting('path');
+ UI.enableSetting('repeaterID');
+ UI.updatePowerButton();
+ UI.keepControlbar();
+ }
+
+ // State change closes the password dialog
+ document.getElementById('noVNC_password_dlg')
+ .classList.remove('noVNC_open');
+ },
+
+ showStatus(text, status_type, time) {
+ const statusElem = document.getElementById('noVNC_status');
+
+ clearTimeout(UI.statusTimeout);
+
+ if (typeof status_type === 'undefined') {
+ status_type = 'normal';
+ }
+
+ // Don't overwrite more severe visible statuses and never
+ // errors. Only shows the first error.
+ let visible_status_type = 'none';
+ if (statusElem.classList.contains("noVNC_open")) {
+ if (statusElem.classList.contains("noVNC_status_error")) {
+ visible_status_type = 'error';
+ } else if (statusElem.classList.contains("noVNC_status_warn")) {
+ visible_status_type = 'warn';
+ } else {
+ visible_status_type = 'normal';
+ }
+ }
+ if (visible_status_type === 'error' ||
+ (visible_status_type === 'warn' && status_type === 'normal')) {
+ return;
+ }
+
+ switch (status_type) {
+ case 'error':
+ statusElem.classList.remove("noVNC_status_warn");
+ statusElem.classList.remove("noVNC_status_normal");
+ statusElem.classList.add("noVNC_status_error");
+ break;
+ case 'warning':
+ case 'warn':
+ statusElem.classList.remove("noVNC_status_error");
+ statusElem.classList.remove("noVNC_status_normal");
+ statusElem.classList.add("noVNC_status_warn");
+ break;
+ case 'normal':
+ case 'info':
+ default:
+ statusElem.classList.remove("noVNC_status_error");
+ statusElem.classList.remove("noVNC_status_warn");
+ statusElem.classList.add("noVNC_status_normal");
+ break;
+ }
+
+ statusElem.textContent = text;
+ statusElem.classList.add("noVNC_open");
+
+ // If no time was specified, show the status for 1.5 seconds
+ if (typeof time === 'undefined') {
+ time = 1500;
+ }
+
+ // Error messages do not timeout
+ if (status_type !== 'error') {
+ UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
+ }
+ },
+
+ hideStatus() {
+ clearTimeout(UI.statusTimeout);
+ document.getElementById('noVNC_status').classList.remove("noVNC_open");
+ },
+
+ activateControlbar(event) {
+ clearTimeout(UI.idleControlbarTimeout);
+ // We manipulate the anchor instead of the actual control
+ // bar in order to avoid creating new a stacking group
+ document.getElementById('noVNC_control_bar_anchor')
+ .classList.remove("noVNC_idle");
+ UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
+ },
+
+ idleControlbar() {
+ document.getElementById('noVNC_control_bar_anchor')
+ .classList.add("noVNC_idle");
+ },
+
+ keepControlbar() {
+ clearTimeout(UI.closeControlbarTimeout);
+ },
+
+ openControlbar() {
+ document.getElementById('noVNC_control_bar')
+ .classList.add("noVNC_open");
+ },
+
+ closeControlbar() {
+ UI.closeAllPanels();
+ document.getElementById('noVNC_control_bar')
+ .classList.remove("noVNC_open");
+ },
+
+ toggleControlbar() {
+ if (document.getElementById('noVNC_control_bar')
+ .classList.contains("noVNC_open")) {
+ UI.closeControlbar();
+ } else {
+ UI.openControlbar();
+ }
+ },
+
+ toggleControlbarSide() {
+ // Temporarily disable animation, if bar is displayed, to avoid weird
+ // movement. The transitionend-event will not fire when display=none.
+ const bar = document.getElementById('noVNC_control_bar');
+ const barDisplayStyle = window.getComputedStyle(bar).display;
+ if (barDisplayStyle !== 'none') {
+ bar.style.transitionDuration = '0s';
+ bar.addEventListener('transitionend', () => bar.style.transitionDuration = '');
+ }
+
+ const anchor = document.getElementById('noVNC_control_bar_anchor');
+ if (anchor.classList.contains("noVNC_right")) {
+ WebUtil.writeSetting('controlbar_pos', 'left');
+ anchor.classList.remove("noVNC_right");
+ } else {
+ WebUtil.writeSetting('controlbar_pos', 'right');
+ anchor.classList.add("noVNC_right");
+ }
+
+ // Consider this a movement of the handle
+ UI.controlbarDrag = true;
+ },
+
+ showControlbarHint(show) {
+ const hint = document.getElementById('noVNC_control_bar_hint');
+ if (show) {
+ hint.classList.add("noVNC_active");
+ } else {
+ hint.classList.remove("noVNC_active");
+ }
+ },
+
+ dragControlbarHandle(e) {
+ if (!UI.controlbarGrabbed) return;
+
+ const ptr = getPointerEvent(e);
+
+ const anchor = document.getElementById('noVNC_control_bar_anchor');
+ if (ptr.clientX < (window.innerWidth * 0.1)) {
+ if (anchor.classList.contains("noVNC_right")) {
+ UI.toggleControlbarSide();
+ }
+ } else if (ptr.clientX > (window.innerWidth * 0.9)) {
+ if (!anchor.classList.contains("noVNC_right")) {
+ UI.toggleControlbarSide();
+ }
+ }
+
+ if (!UI.controlbarDrag) {
+ const dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
+
+ if (dragDistance < dragThreshold) return;
+
+ UI.controlbarDrag = true;
+ }
+
+ const eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;
+
+ UI.moveControlbarHandle(eventY);
+
+ e.preventDefault();
+ e.stopPropagation();
+ UI.keepControlbar();
+ UI.activateControlbar();
+ },
+
+ // Move the handle but don't allow any position outside the bounds
+ moveControlbarHandle(viewportRelativeY) {
+ const handle = document.getElementById("noVNC_control_bar_handle");
+ const handleHeight = handle.getBoundingClientRect().height;
+ const controlbarBounds = document.getElementById("noVNC_control_bar")
+ .getBoundingClientRect();
+ const margin = 10;
+
+ // These heights need to be non-zero for the below logic to work
+ if (handleHeight === 0 || controlbarBounds.height === 0) {
+ return;
+ }
+
+ let newY = viewportRelativeY;
+
+ // Check if the coordinates are outside the control bar
+ if (newY < controlbarBounds.top + margin) {
+ // Force coordinates to be below the top of the control bar
+ newY = controlbarBounds.top + margin;
+
+ } else if (newY > controlbarBounds.top +
+ controlbarBounds.height - handleHeight - margin) {
+ // Force coordinates to be above the bottom of the control bar
+ newY = controlbarBounds.top +
+ controlbarBounds.height - handleHeight - margin;
+ }
+
+ // Corner case: control bar too small for stable position
+ if (controlbarBounds.height < (handleHeight + margin * 2)) {
+ newY = controlbarBounds.top +
+ (controlbarBounds.height - handleHeight) / 2;
+ }
+
+ // The transform needs coordinates that are relative to the parent
+ const parentRelativeY = newY - controlbarBounds.top;
+ handle.style.transform = "translateY(" + parentRelativeY + "px)";
+ },
+
+ updateControlbarHandle() {
+ // Since the control bar is fixed on the viewport and not the page,
+ // the move function expects coordinates relative the the viewport.
+ const handle = document.getElementById("noVNC_control_bar_handle");
+ const handleBounds = handle.getBoundingClientRect();
+ UI.moveControlbarHandle(handleBounds.top);
+ },
+
+ controlbarHandleMouseUp(e) {
+ if ((e.type == "mouseup") && (e.button != 0)) return;
+
+ // mouseup and mousedown on the same place toggles the controlbar
+ if (UI.controlbarGrabbed && !UI.controlbarDrag) {
+ UI.toggleControlbar();
+ e.preventDefault();
+ e.stopPropagation();
+ UI.keepControlbar();
+ UI.activateControlbar();
+ }
+ UI.controlbarGrabbed = false;
+ UI.showControlbarHint(false);
+ },
+
+ controlbarHandleMouseDown(e) {
+ if ((e.type == "mousedown") && (e.button != 0)) return;
+
+ const ptr = getPointerEvent(e);
+
+ const handle = document.getElementById("noVNC_control_bar_handle");
+ const bounds = handle.getBoundingClientRect();
+
+ // Touch events have implicit capture
+ if (e.type === "mousedown") {
+ setCapture(handle);
+ }
+
+ UI.controlbarGrabbed = true;
+ UI.controlbarDrag = false;
+
+ UI.showControlbarHint(true);
+
+ UI.controlbarMouseDownClientY = ptr.clientY;
+ UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
+ e.preventDefault();
+ e.stopPropagation();
+ UI.keepControlbar();
+ UI.activateControlbar();
+ },
+
+ toggleExpander(e) {
+ if (this.classList.contains("noVNC_open")) {
+ this.classList.remove("noVNC_open");
+ } else {
+ this.classList.add("noVNC_open");
+ }
+ },
+
+/* ------^-------
+ * /VISUAL
+ * ==============
+ * SETTINGS
+ * ------v------*/
+
+ // Initial page load read/initialization of settings
+ initSetting(name, defVal) {
+ // Check Query string followed by cookie
+ let val = WebUtil.getConfigVar(name);
+ if (val === null) {
+ val = WebUtil.readSetting(name, defVal);
+ }
+ WebUtil.setSetting(name, val);
+ UI.updateSetting(name);
+ return val;
+ },
+
+ // Set the new value, update and disable form control setting
+ forceSetting(name, val) {
+ WebUtil.setSetting(name, val);
+ UI.updateSetting(name);
+ UI.disableSetting(name);
+ },
+
+ // Update cookie and form control setting. If value is not set, then
+ // updates from control to current cookie setting.
+ updateSetting(name) {
+
+ // Update the settings control
+ let value = UI.getSetting(name);
+
+ const ctrl = document.getElementById('noVNC_setting_' + name);
+ if (ctrl.type === 'checkbox') {
+ ctrl.checked = value;
+
+ } else if (typeof ctrl.options !== 'undefined') {
+ for (let i = 0; i < ctrl.options.length; i += 1) {
+ if (ctrl.options[i].value === value) {
+ ctrl.selectedIndex = i;
+ break;
+ }
+ }
+ } else {
+ /*Weird IE9 error leads to 'null' appearring
+ in textboxes instead of ''.*/
+ if (value === null) {
+ value = "";
+ }
+ ctrl.value = value;
+ }
+ },
+
+ // Save control setting to cookie
+ saveSetting(name) {
+ const ctrl = document.getElementById('noVNC_setting_' + name);
+ let val;
+ if (ctrl.type === 'checkbox') {
+ val = ctrl.checked;
+ } else if (typeof ctrl.options !== 'undefined') {
+ val = ctrl.options[ctrl.selectedIndex].value;
+ } else {
+ val = ctrl.value;
+ }
+ WebUtil.writeSetting(name, val);
+ //Log.Debug("Setting saved '" + name + "=" + val + "'");
+ return val;
+ },
+
+ // Read form control compatible setting from cookie
+ getSetting(name) {
+ const ctrl = document.getElementById('noVNC_setting_' + name);
+ let val = WebUtil.readSetting(name);
+ if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
+ if (val.toString().toLowerCase() in {'0': 1, 'no': 1, 'false': 1}) {
+ val = false;
+ } else {
+ val = true;
+ }
+ }
+ return val;
+ },
+
+ // These helpers compensate for the lack of parent-selectors and
+ // previous-sibling-selectors in CSS which are needed when we want to
+ // disable the labels that belong to disabled input elements.
+ disableSetting(name) {
+ const ctrl = document.getElementById('noVNC_setting_' + name);
+ ctrl.disabled = true;
+ ctrl.label.classList.add('noVNC_disabled');
+ },
+
+ enableSetting(name) {
+ const ctrl = document.getElementById('noVNC_setting_' + name);
+ ctrl.disabled = false;
+ ctrl.label.classList.remove('noVNC_disabled');
+ },
+
+/* ------^-------
+ * /SETTINGS
+ * ==============
+ * PANELS
+ * ------v------*/
+
+ closeAllPanels() {
+ UI.closeSettingsPanel();
+ UI.closePowerPanel();
+ UI.closeClipboardPanel();
+ UI.closeExtraKeys();
+ },
+
+/* ------^-------
+ * /PANELS
+ * ==============
+ * SETTINGS (panel)
+ * ------v------*/
+
+ openSettingsPanel() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ // Refresh UI elements from saved cookies
+ UI.updateSetting('encrypt');
+ UI.updateSetting('view_clip');
+ UI.updateSetting('resize');
+ UI.updateSetting('shared');
+ UI.updateSetting('view_only');
+ UI.updateSetting('path');
+ UI.updateSetting('repeaterID');
+ UI.updateSetting('logging');
+ UI.updateSetting('reconnect');
+ UI.updateSetting('reconnect_delay');
+
+ document.getElementById('noVNC_settings')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_settings_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closeSettingsPanel() {
+ document.getElementById('noVNC_settings')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_settings_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ toggleSettingsPanel() {
+ if (document.getElementById('noVNC_settings')
+ .classList.contains("noVNC_open")) {
+ UI.closeSettingsPanel();
+ } else {
+ UI.openSettingsPanel();
+ }
+ },
+
+/* ------^-------
+ * /SETTINGS
+ * ==============
+ * POWER
+ * ------v------*/
+
+ openPowerPanel() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ document.getElementById('noVNC_power')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_power_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closePowerPanel() {
+ document.getElementById('noVNC_power')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_power_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ togglePowerPanel() {
+ if (document.getElementById('noVNC_power')
+ .classList.contains("noVNC_open")) {
+ UI.closePowerPanel();
+ } else {
+ UI.openPowerPanel();
+ }
+ },
+
+ // Disable/enable power button
+ updatePowerButton() {
+ if (UI.connected &&
+ UI.rfb.capabilities.power &&
+ !UI.rfb.viewOnly) {
+ document.getElementById('noVNC_power_button')
+ .classList.remove("noVNC_hidden");
+ } else {
+ document.getElementById('noVNC_power_button')
+ .classList.add("noVNC_hidden");
+ // Close power panel if open
+ UI.closePowerPanel();
+ }
+ },
+
+/* ------^-------
+ * /POWER
+ * ==============
+ * CLIPBOARD
+ * ------v------*/
+
+ openClipboardPanel() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ document.getElementById('noVNC_clipboard')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_clipboard_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closeClipboardPanel() {
+ document.getElementById('noVNC_clipboard')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_clipboard_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ toggleClipboardPanel() {
+ if (document.getElementById('noVNC_clipboard')
+ .classList.contains("noVNC_open")) {
+ UI.closeClipboardPanel();
+ } else {
+ UI.openClipboardPanel();
+ }
+ },
+
+ clipboardReceive(e) {
+ Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0, 40) + "...");
+ document.getElementById('noVNC_clipboard_text').value = e.detail.text;
+ Log.Debug("<< UI.clipboardReceive");
+ },
+
+ clipboardClear() {
+ document.getElementById('noVNC_clipboard_text').value = "";
+ UI.rfb.clipboardPasteFrom("");
+ },
+
+ clipboardSend() {
+ const text = document.getElementById('noVNC_clipboard_text').value;
+ Log.Debug(">> UI.clipboardSend: " + text.substr(0, 40) + "...");
+ UI.rfb.clipboardPasteFrom(text);
+ Log.Debug("<< UI.clipboardSend");
+ },
+
+/* ------^-------
+ * /CLIPBOARD
+ * ==============
+ * CONNECTION
+ * ------v------*/
+
+ openConnectPanel() {
+ document.getElementById('noVNC_connect_dlg')
+ .classList.add("noVNC_open");
+ },
+
+ closeConnectPanel() {
+ document.getElementById('noVNC_connect_dlg')
+ .classList.remove("noVNC_open");
+ },
+
+ connect(event, password) {
+
+ // Ignore when rfb already exists
+ if (typeof UI.rfb !== 'undefined') {
+ return;
+ }
+
+ const host = UI.getSetting('host');
+ const port = UI.getSetting('port');
+ const path = UI.getSetting('path');
+
+ if (typeof password === 'undefined') {
+ password = WebUtil.getConfigVar('password');
+ UI.reconnect_password = password;
+ }
+
+ if (password === null) {
+ password = undefined;
+ }
+
+ UI.hideStatus();
+
+ if (!host) {
+ Log.Error("Can't connect when host is: " + host);
+ UI.showStatus(_("Must set host"), 'error');
+ return;
+ }
+
+ UI.closeAllPanels();
+ UI.closeConnectPanel();
+
+ UI.updateVisualState('connecting');
+
+ let url;
+
+ url = UI.getSetting('encrypt') ? 'wss' : 'ws';
+
+ url += '://' + host;
+ if (port) {
+ url += ':' + port;
+ }
+ url += '/' + path;
+
+ var urlParams = new URLSearchParams(window.location.search);
+ var param = urlParams.get('token');
+ if (param) {
+ url += "?token=" + param
+ }
+
+ UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
+ { shared: UI.getSetting('shared'),
+ showDotCursor: UI.getSetting('show_dot'),
+ repeaterID: UI.getSetting('repeaterID'),
+ credentials: { password: password } });
+ UI.rfb.addEventListener("connect", UI.connectFinished);
+ UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
+ UI.rfb.addEventListener("credentialsrequired", UI.credentials);
+ UI.rfb.addEventListener("securityfailure", UI.securityFailed);
+ UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
+ UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
+ UI.rfb.addEventListener("bell", UI.bell);
+ UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
+ UI.rfb.clipViewport = UI.getSetting('view_clip');
+ UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+ UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
+
+ UI.updateViewOnly(); // requires UI.rfb
+ },
+
+ disconnect() {
+ UI.closeAllPanels();
+ UI.rfb.disconnect();
+
+ UI.connected = false;
+
+ // Disable automatic reconnecting
+ UI.inhibit_reconnect = true;
+
+ UI.updateVisualState('disconnecting');
+
+ // Don't display the connection settings until we're actually disconnected
+ },
+
+ reconnect() {
+ UI.reconnect_callback = null;
+
+ // if reconnect has been disabled in the meantime, do nothing.
+ if (UI.inhibit_reconnect) {
+ return;
+ }
+
+ UI.connect(null, UI.reconnect_password);
+ },
+
+ cancelReconnect() {
+ if (UI.reconnect_callback !== null) {
+ clearTimeout(UI.reconnect_callback);
+ UI.reconnect_callback = null;
+ }
+
+ UI.updateVisualState('disconnected');
+
+ UI.openControlbar();
+ UI.openConnectPanel();
+ },
+
+ connectFinished(e) {
+ UI.connected = true;
+ UI.inhibit_reconnect = false;
+
+ let msg;
+ if (UI.getSetting('encrypt')) {
+ msg = _("Connected (encrypted) to ") + UI.desktopName;
+ } else {
+ msg = _("Connected (unencrypted) to ") + UI.desktopName;
+ }
+ UI.showStatus(msg);
+ UI.updateVisualState('connected');
+
+ // Do this last because it can only be used on rendered elements
+ UI.rfb.focus();
+ },
+
+ disconnectFinished(e) {
+ const wasConnected = UI.connected;
+
+ // This variable is ideally set when disconnection starts, but
+ // when the disconnection isn't clean or if it is initiated by
+ // the server, we need to do it here as well since
+ // UI.disconnect() won't be used in those cases.
+ UI.connected = false;
+
+ UI.rfb = undefined;
+
+ if (!e.detail.clean) {
+ UI.updateVisualState('disconnected');
+ if (wasConnected) {
+ UI.showStatus(_("Something went wrong, connection is closed"),
+ 'error');
+ } else {
+ UI.showStatus(_("Failed to connect to server"), 'error');
+ }
+ } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
+ UI.updateVisualState('reconnecting');
+
+ const delay = parseInt(UI.getSetting('reconnect_delay'));
+ UI.reconnect_callback = setTimeout(UI.reconnect, delay);
+ return;
+ } else {
+ UI.updateVisualState('disconnected');
+ UI.showStatus(_("Disconnected"), 'normal');
+ }
+
+ UI.openControlbar();
+ UI.openConnectPanel();
+ },
+
+ securityFailed(e) {
+ let msg = "";
+ // On security failures we might get a string with a reason
+ // directly from the server. Note that we can't control if
+ // this string is translated or not.
+ if ('reason' in e.detail) {
+ msg = _("New connection has been rejected with reason: ") +
+ e.detail.reason;
+ } else {
+ msg = _("New connection has been rejected");
+ }
+ UI.showStatus(msg, 'error');
+ },
+
+/* ------^-------
+ * /CONNECTION
+ * ==============
+ * PASSWORD
+ * ------v------*/
+
+ credentials(e) {
+ // FIXME: handle more types
+ document.getElementById('noVNC_password_dlg')
+ .classList.add('noVNC_open');
+
+ setTimeout(() => document
+ .getElementById('noVNC_password_input').focus(), 100);
+
+ Log.Warn("Server asked for a password");
+ UI.showStatus(_("Password is required"), "warning");
+ },
+
+ setPassword(e) {
+ // Prevent actually submitting the form
+ e.preventDefault();
+
+ const inputElem = document.getElementById('noVNC_password_input');
+ const password = inputElem.value;
+ // Clear the input after reading the password
+ inputElem.value = "";
+ UI.rfb.sendCredentials({ password: password });
+ UI.reconnect_password = password;
+ document.getElementById('noVNC_password_dlg')
+ .classList.remove('noVNC_open');
+ },
+
+/* ------^-------
+ * /PASSWORD
+ * ==============
+ * FULLSCREEN
+ * ------v------*/
+
+ toggleFullscreen() {
+ if (document.fullscreenElement || // alternative standard method
+ document.mozFullScreenElement || // currently working methods
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement) {
+ if (document.exitFullscreen) {
+ document.exitFullscreen();
+ } else if (document.mozCancelFullScreen) {
+ document.mozCancelFullScreen();
+ } else if (document.webkitExitFullscreen) {
+ document.webkitExitFullscreen();
+ } else if (document.msExitFullscreen) {
+ document.msExitFullscreen();
+ }
+ } else {
+ if (document.documentElement.requestFullscreen) {
+ document.documentElement.requestFullscreen();
+ } else if (document.documentElement.mozRequestFullScreen) {
+ document.documentElement.mozRequestFullScreen();
+ } else if (document.documentElement.webkitRequestFullscreen) {
+ document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ } else if (document.body.msRequestFullscreen) {
+ document.body.msRequestFullscreen();
+ }
+ }
+ UI.updateFullscreenButton();
+ },
+
+ updateFullscreenButton() {
+ if (document.fullscreenElement || // alternative standard method
+ document.mozFullScreenElement || // currently working methods
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement ) {
+ document.getElementById('noVNC_fullscreen_button')
+ .classList.add("noVNC_selected");
+ } else {
+ document.getElementById('noVNC_fullscreen_button')
+ .classList.remove("noVNC_selected");
+ }
+ },
+
+/* ------^-------
+ * /FULLSCREEN
+ * ==============
+ * RESIZE
+ * ------v------*/
+
+ // Apply remote resizing or local scaling
+ applyResizeMode() {
+ if (!UI.rfb) return;
+
+ UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+ UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
+ },
+
+/* ------^-------
+ * /RESIZE
+ * ==============
+ * VIEW CLIPPING
+ * ------v------*/
+
+ // Update viewport clipping property for the connection. The normal
+ // case is to get the value from the setting. There are special cases
+ // for when the viewport is scaled or when a touch device is used.
+ updateViewClip() {
+ if (!UI.rfb) return;
+
+ const scaling = UI.getSetting('resize') === 'scale';
+
+ if (scaling) {
+ // Can't be clipping if viewport is scaled to fit
+ UI.forceSetting('view_clip', false);
+ UI.rfb.clipViewport = false;
+ } else if (isIOS() || isAndroid()) {
+ // iOS and Android usually have shit scrollbars
+ UI.forceSetting('view_clip', true);
+ UI.rfb.clipViewport = true;
+ } else {
+ UI.enableSetting('view_clip');
+ UI.rfb.clipViewport = UI.getSetting('view_clip');
+ }
+
+ // Changing the viewport may change the state of
+ // the dragging button
+ UI.updateViewDrag();
+ },
+
+/* ------^-------
+ * /VIEW CLIPPING
+ * ==============
+ * VIEWDRAG
+ * ------v------*/
+
+ toggleViewDrag() {
+ if (!UI.rfb) return;
+
+ UI.rfb.dragViewport = !UI.rfb.dragViewport;
+ UI.updateViewDrag();
+ },
+
+ updateViewDrag() {
+ if (!UI.connected) return;
+
+ const viewDragButton = document.getElementById('noVNC_view_drag_button');
+
+ if (!UI.rfb.clipViewport && UI.rfb.dragViewport) {
+ // We are no longer clipping the viewport. Make sure
+ // viewport drag isn't active when it can't be used.
+ UI.rfb.dragViewport = false;
+ }
+
+ if (UI.rfb.dragViewport) {
+ viewDragButton.classList.add("noVNC_selected");
+ } else {
+ viewDragButton.classList.remove("noVNC_selected");
+ }
+
+ // Different behaviour for touch vs non-touch
+ // The button is disabled instead of hidden on touch devices
+ if (isTouchDevice) {
+ viewDragButton.classList.remove("noVNC_hidden");
+
+ if (UI.rfb.clipViewport) {
+ viewDragButton.disabled = false;
+ } else {
+ viewDragButton.disabled = true;
+ }
+ } else {
+ viewDragButton.disabled = false;
+
+ if (UI.rfb.clipViewport) {
+ viewDragButton.classList.remove("noVNC_hidden");
+ } else {
+ viewDragButton.classList.add("noVNC_hidden");
+ }
+ }
+ },
+
+/* ------^-------
+ * /VIEWDRAG
+ * ==============
+ * KEYBOARD
+ * ------v------*/
+
+ showVirtualKeyboard() {
+ if (!isTouchDevice) return;
+
+ const input = document.getElementById('noVNC_keyboardinput');
+
+ if (document.activeElement == input) return;
+
+ input.focus();
+
+ try {
+ const l = input.value.length;
+ // Move the caret to the end
+ input.setSelectionRange(l, l);
+ } catch (err) {
+ // setSelectionRange is undefined in Google Chrome
+ }
+ },
+
+ hideVirtualKeyboard() {
+ if (!isTouchDevice) return;
+
+ const input = document.getElementById('noVNC_keyboardinput');
+
+ if (document.activeElement != input) return;
+
+ input.blur();
+ },
+
+ toggleVirtualKeyboard() {
+ if (document.getElementById('noVNC_keyboard_button')
+ .classList.contains("noVNC_selected")) {
+ UI.hideVirtualKeyboard();
+ } else {
+ UI.showVirtualKeyboard();
+ }
+ },
+
+ onfocusVirtualKeyboard(event) {
+ document.getElementById('noVNC_keyboard_button')
+ .classList.add("noVNC_selected");
+ if (UI.rfb) {
+ UI.rfb.focusOnClick = false;
+ }
+ },
+
+ onblurVirtualKeyboard(event) {
+ document.getElementById('noVNC_keyboard_button')
+ .classList.remove("noVNC_selected");
+ if (UI.rfb) {
+ UI.rfb.focusOnClick = true;
+ }
+ },
+
+ keepVirtualKeyboard(event) {
+ const input = document.getElementById('noVNC_keyboardinput');
+
+ // Only prevent focus change if the virtual keyboard is active
+ if (document.activeElement != input) {
+ return;
+ }
+
+ // Only allow focus to move to other elements that need
+ // focus to function properly
+ if (event.target.form !== undefined) {
+ switch (event.target.type) {
+ case 'text':
+ case 'email':
+ case 'search':
+ case 'password':
+ case 'tel':
+ case 'url':
+ case 'textarea':
+ case 'select-one':
+ case 'select-multiple':
+ return;
+ }
+ }
+
+ event.preventDefault();
+ },
+
+ keyboardinputReset() {
+ const kbi = document.getElementById('noVNC_keyboardinput');
+ kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
+ UI.lastKeyboardinput = kbi.value;
+ },
+
+ keyEvent(keysym, code, down) {
+ if (!UI.rfb) return;
+
+ UI.rfb.sendKey(keysym, code, down);
+ },
+
+ // When normal keyboard events are left uncought, use the input events from
+ // the keyboardinput element instead and generate the corresponding key events.
+ // This code is required since some browsers on Android are inconsistent in
+ // sending keyCodes in the normal keyboard events when using on screen keyboards.
+ keyInput(event) {
+
+ if (!UI.rfb) return;
+
+ const newValue = event.target.value;
+
+ if (!UI.lastKeyboardinput) {
+ UI.keyboardinputReset();
+ }
+ const oldValue = UI.lastKeyboardinput;
+
+ let newLen;
+ try {
+ // Try to check caret position since whitespace at the end
+ // will not be considered by value.length in some browsers
+ newLen = Math.max(event.target.selectionStart, newValue.length);
+ } catch (err) {
+ // selectionStart is undefined in Google Chrome
+ newLen = newValue.length;
+ }
+ const oldLen = oldValue.length;
+
+ let inputs = newLen - oldLen;
+ let backspaces = inputs < 0 ? -inputs : 0;
+
+ // Compare the old string with the new to account for
+ // text-corrections or other input that modify existing text
+ for (let i = 0; i < Math.min(oldLen, newLen); i++) {
+ if (newValue.charAt(i) != oldValue.charAt(i)) {
+ inputs = newLen - i;
+ backspaces = oldLen - i;
+ break;
+ }
+ }
+
+ // Send the key events
+ for (let i = 0; i < backspaces; i++) {
+ UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
+ }
+ for (let i = newLen - inputs; i < newLen; i++) {
+ UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));
+ }
+
+ // Control the text content length in the keyboardinput element
+ if (newLen > 2 * UI.defaultKeyboardinputLen) {
+ UI.keyboardinputReset();
+ } else if (newLen < 1) {
+ // There always have to be some text in the keyboardinput
+ // element with which backspace can interact.
+ UI.keyboardinputReset();
+ // This sometimes causes the keyboard to disappear for a second
+ // but it is required for the android keyboard to recognize that
+ // text has been added to the field
+ event.target.blur();
+ // This has to be ran outside of the input handler in order to work
+ setTimeout(event.target.focus.bind(event.target), 0);
+ } else {
+ UI.lastKeyboardinput = newValue;
+ }
+ },
+
+/* ------^-------
+ * /KEYBOARD
+ * ==============
+ * EXTRA KEYS
+ * ------v------*/
+
+ openExtraKeys() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ document.getElementById('noVNC_modifiers')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_toggle_extra_keys_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closeExtraKeys() {
+ document.getElementById('noVNC_modifiers')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_toggle_extra_keys_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ toggleExtraKeys() {
+ if (document.getElementById('noVNC_modifiers')
+ .classList.contains("noVNC_open")) {
+ UI.closeExtraKeys();
+ } else {
+ UI.openExtraKeys();
+ }
+ },
+
+ sendEsc() {
+ UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
+ },
+
+ sendTab() {
+ UI.rfb.sendKey(KeyTable.XK_Tab);
+ },
+
+ toggleCtrl() {
+ const btn = document.getElementById('noVNC_toggle_ctrl_button');
+ if (btn.classList.contains("noVNC_selected")) {
+ UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+ btn.classList.remove("noVNC_selected");
+ } else {
+ UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+ btn.classList.add("noVNC_selected");
+ }
+ },
+
+ toggleWindows() {
+ const btn = document.getElementById('noVNC_toggle_windows_button');
+ if (btn.classList.contains("noVNC_selected")) {
+ UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
+ btn.classList.remove("noVNC_selected");
+ } else {
+ UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
+ btn.classList.add("noVNC_selected");
+ }
+ },
+
+ toggleAlt() {
+ const btn = document.getElementById('noVNC_toggle_alt_button');
+ if (btn.classList.contains("noVNC_selected")) {
+ UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+ btn.classList.remove("noVNC_selected");
+ } else {
+ UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+ btn.classList.add("noVNC_selected");
+ }
+ },
+
+ sendCtrlAltDel() {
+ UI.rfb.sendCtrlAltDel();
+ },
+
+/* ------^-------
+ * /EXTRA KEYS
+ * ==============
+ * MISC
+ * ------v------*/
+
+ setMouseButton(num) {
+ const view_only = UI.rfb.viewOnly;
+ if (UI.rfb && !view_only) {
+ UI.rfb.touchButton = num;
+ }
+
+ const blist = [0, 1, 2, 4];
+ for (let b = 0; b < blist.length; b++) {
+ const button = document.getElementById('noVNC_mouse_button' +
+ blist[b]);
+ if (blist[b] === num && !view_only) {
+ button.classList.remove("noVNC_hidden");
+ } else {
+ button.classList.add("noVNC_hidden");
+ }
+ }
+ },
+
+ updateViewOnly() {
+ if (!UI.rfb) return;
+ UI.rfb.viewOnly = UI.getSetting('view_only');
+
+ // Hide input related buttons in view only mode
+ if (UI.rfb.viewOnly) {
+ document.getElementById('noVNC_keyboard_button')
+ .classList.add('noVNC_hidden');
+ document.getElementById('noVNC_toggle_extra_keys_button')
+ .classList.add('noVNC_hidden');
+ document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
+ .classList.add('noVNC_hidden');
+ } else {
+ document.getElementById('noVNC_keyboard_button')
+ .classList.remove('noVNC_hidden');
+ document.getElementById('noVNC_toggle_extra_keys_button')
+ .classList.remove('noVNC_hidden');
+ document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
+ .classList.remove('noVNC_hidden');
+ }
+ },
+
+ updateShowDotCursor() {
+ if (!UI.rfb) return;
+ UI.rfb.showDotCursor = UI.getSetting('show_dot');
+ },
+
+ updateLogging() {
+ WebUtil.init_logging(UI.getSetting('logging'));
+ },
+
+ updateDesktopName(e) {
+ UI.desktopName = e.detail.name;
+ // Display the desktop name in the document title
+ document.title = e.detail.name + " - noVNC";
+ },
+
+ bell(e) {
+ if (WebUtil.getConfigVar('bell', 'on') === 'on') {
+ const promise = document.getElementById('noVNC_bell').play();
+ // The standards disagree on the return value here
+ if (promise) {
+ promise.catch((e) => {
+ if (e.name === "NotAllowedError") {
+ // Ignore when the browser doesn't let us play audio.
+ // It is common that the browsers require audio to be
+ // initiated from a user action.
+ } else {
+ Log.Error("Unable to play bell: " + e);
+ }
+ });
+ }
+ }
+ },
+
+ //Helper to add options to dropdown.
+ addOption(selectbox, text, value) {
+ const optn = document.createElement("OPTION");
+ optn.text = text;
+ optn.value = value;
+ selectbox.options.add(optn);
+ },
+
+/* ------^-------
+ * /MISC
+ * ==============
+ */
+};
+
+// Set up translations
+const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
+l10n.setup(LINGUAS);
+if (l10n.language === "en" || l10n.dictionary !== undefined) {
+ UI.prime();
+} else {
+ WebUtil.fetchJSON('app/locale/' + l10n.language + '.json')
+ .then((translations) => { l10n.dictionary = translations; })
+ .catch(err => Log.Error("Failed to load translations: " + err))
+ .then(UI.prime);
+}
+
+export default UI;
diff --git a/systemvm/agent/noVNC/app/webutil.js b/systemvm/agent/noVNC/app/webutil.js
new file mode 100644
index 0000000..98e1d9e
--- /dev/null
+++ b/systemvm/agent/noVNC/app/webutil.js
@@ -0,0 +1,239 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+import { init_logging as main_init_logging } from '../core/util/logging.js';
+
+// init log level reading the logging HTTP param
+export function init_logging(level) {
+ "use strict";
+ if (typeof level !== "undefined") {
+ main_init_logging(level);
+ } else {
+ const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
+ main_init_logging(param || undefined);
+ }
+}
+
+// Read a query string variable
+export function getQueryVar(name, defVal) {
+ "use strict";
+ const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
+ match = document.location.href.match(re);
+ if (typeof defVal === 'undefined') { defVal = null; }
+
+ if (match) {
+ return decodeURIComponent(match[1]);
+ }
+
+ return defVal;
+}
+
+// Read a hash fragment variable
+export function getHashVar(name, defVal) {
+ "use strict";
+ const re = new RegExp('.*[&#]' + name + '=([^&]*)'),
+ match = document.location.hash.match(re);
+ if (typeof defVal === 'undefined') { defVal = null; }
+
+ if (match) {
+ return decodeURIComponent(match[1]);
+ }
+
+ return defVal;
+}
+
+// Read a variable from the fragment or the query string
+// Fragment takes precedence
+export function getConfigVar(name, defVal) {
+ "use strict";
+ const val = getHashVar(name);
+
+ if (val === null) {
+ return getQueryVar(name, defVal);
+ }
+
+ return val;
+}
+
+/*
+ * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
+ */
+
+// No days means only for this browser session
+export function createCookie(name, value, days) {
+ "use strict";
+ let date, expires;
+ if (days) {
+ date = new Date();
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+ expires = "; expires=" + date.toGMTString();
+ } else {
+ expires = "";
+ }
+
+ let secure;
+ if (document.location.protocol === "https:") {
+ secure = "; secure";
+ } else {
+ secure = "";
+ }
+ document.cookie = name + "=" + value + expires + "; path=/" + secure;
+}
+
+export function readCookie(name, defaultValue) {
+ "use strict";
+ const nameEQ = name + "=";
+ const ca = document.cookie.split(';');
+
+ for (let i = 0; i < ca.length; i += 1) {
+ let c = ca[i];
+ while (c.charAt(0) === ' ') {
+ c = c.substring(1, c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length, c.length);
+ }
+ }
+
+ return (typeof defaultValue !== 'undefined') ? defaultValue : null;
+}
+
+export function eraseCookie(name) {
+ "use strict";
+ createCookie(name, "", -1);
+}
+
+/*
+ * Setting handling.
+ */
+
+let settings = {};
+
+export function initSettings() {
+ if (!window.chrome || !window.chrome.storage) {
+ settings = {};
+ return Promise.resolve();
+ }
+
+ return new Promise(resolve => window.chrome.storage.sync.get(resolve))
+ .then((cfg) => { settings = cfg; });
+}
+
+// Update the settings cache, but do not write to permanent storage
+export function setSetting(name, value) {
+ settings[name] = value;
+}
+
+// No days means only for this browser session
+export function writeSetting(name, value) {
+ "use strict";
+ if (settings[name] === value) return;
+ settings[name] = value;
+ if (window.chrome && window.chrome.storage) {
+ window.chrome.storage.sync.set(settings);
+ } else {
+ localStorage.setItem(name, value);
+ }
+}
+
+export function readSetting(name, defaultValue) {
+ "use strict";
+ let value;
+ if ((name in settings) || (window.chrome && window.chrome.storage)) {
+ value = settings[name];
+ } else {
+ value = localStorage.getItem(name);
+ settings[name] = value;
+ }
+ if (typeof value === "undefined") {
+ value = null;
+ }
+
+ if (value === null && typeof defaultValue !== "undefined") {
+ return defaultValue;
+ }
+
+ return value;
+}
+
+export function eraseSetting(name) {
+ "use strict";
+ // Deleting here means that next time the setting is read when using local
+ // storage, it will be pulled from local storage again.
+ // If the setting in local storage is changed (e.g. in another tab)
+ // between this delete and the next read, it could lead to an unexpected
+ // value change.
+ delete settings[name];
+ if (window.chrome && window.chrome.storage) {
+ window.chrome.storage.sync.remove(name);
+ } else {
+ localStorage.removeItem(name);
+ }
+}
+
+export function injectParamIfMissing(path, param, value) {
+ // force pretend that we're dealing with a relative path
+ // (assume that we wanted an extra if we pass one in)
+ path = "/" + path;
+
+ const elem = document.createElement('a');
+ elem.href = path;
+
+ const param_eq = encodeURIComponent(param) + "=";
+ let query;
+ if (elem.search) {
+ query = elem.search.slice(1).split('&');
+ } else {
+ query = [];
+ }
+
+ if (!query.some(v => v.startsWith(param_eq))) {
+ query.push(param_eq + encodeURIComponent(value));
+ elem.search = "?" + query.join("&");
+ }
+
+ // some browsers (e.g. IE11) may occasionally omit the leading slash
+ // in the elem.pathname string. Handle that case gracefully.
+ if (elem.pathname.charAt(0) == "/") {
+ return elem.pathname.slice(1) + elem.search + elem.hash;
+ }
+
+ return elem.pathname + elem.search + elem.hash;
+}
+
+// sadly, we can't use the Fetch API until we decide to drop
+// IE11 support or polyfill promises and fetch in IE11.
+// resolve will receive an object on success, while reject
+// will receive either an event or an error on failure.
+export function fetchJSON(path) {
+ return new Promise((resolve, reject) => {
+ // NB: IE11 doesn't support JSON as a responseType
+ const req = new XMLHttpRequest();
+ req.open('GET', path);
+
+ req.onload = () => {
+ if (req.status === 200) {
+ let resObj;
+ try {
+ resObj = JSON.parse(req.responseText);
+ } catch (err) {
+ reject(err);
+ }
+ resolve(resObj);
+ } else {
+ reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
+ }
+ };
+
+ req.onerror = evt => reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
+
+ req.ontimeout = evt => reject(new Error("XHR timed out while trying to load '" + path + "'"));
+
+ req.send();
+ });
+}
diff --git a/systemvm/agent/noVNC/core/base64.js b/systemvm/agent/noVNC/core/base64.js
new file mode 100644
index 0000000..88e7454
--- /dev/null
+++ b/systemvm/agent/noVNC/core/base64.js
@@ -0,0 +1,104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
+
+import * as Log from './util/logging.js';
+
+export default {
+ /* Convert data (an array of integers) to a Base64 string. */
+ toBase64Table: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
+ base64Pad: '=',
+
+ encode(data) {
+ "use strict";
+ let result = '';
+ const length = data.length;
+ const lengthpad = (length % 3);
+ // Convert every three bytes to 4 ascii characters.
+
+ for (let i = 0; i < (length - 2); i += 3) {
+ result += this.toBase64Table[data[i] >> 2];
+ result += this.toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
+ result += this.toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
+ result += this.toBase64Table[data[i + 2] & 0x3f];
+ }
+
+ // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+ const j = length - lengthpad;
+ if (lengthpad === 2) {
+ result += this.toBase64Table[data[j] >> 2];
+ result += this.toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
+ result += this.toBase64Table[(data[j + 1] & 0x0f) << 2];
+ result += this.toBase64Table[64];
+ } else if (lengthpad === 1) {
+ result += this.toBase64Table[data[j] >> 2];
+ result += this.toBase64Table[(data[j] & 0x03) << 4];
+ result += this.toBase64Table[64];
+ result += this.toBase64Table[64];
+ }
+
+ return result;
+ },
+
+ /* Convert Base64 data to a string */
+ /* eslint-disable comma-spacing */
+ toBinaryTable: [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+ ],
+ /* eslint-enable comma-spacing */
+
+ decode(data, offset = 0) {
+ let data_length = data.indexOf('=') - offset;
+ if (data_length < 0) { data_length = data.length - offset; }
+
+ /* Every four characters is 3 resulting numbers */
+ const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
+ const result = new Array(result_length);
+
+ // Convert one by one.
+
+ let leftbits = 0; // number of bits decoded, but yet to be appended
+ let leftdata = 0; // bits decoded, but yet to be appended
+ for (let idx = 0, i = offset; i < data.length; i++) {
+ const c = this.toBinaryTable[data.charCodeAt(i) & 0x7f];
+ const padding = (data.charAt(i) === this.base64Pad);
+ // Skip illegal characters and whitespace
+ if (c === -1) {
+ Log.Error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
+ continue;
+ }
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding) {
+ result[idx++] = (leftdata >> leftbits) & 0xff;
+ }
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits) {
+ const err = new Error('Corrupted base64 string');
+ err.name = 'Base64-Error';
+ throw err;
+ }
+
+ return result;
+ }
+}; /* End of Base64 namespace */
diff --git a/systemvm/agent/noVNC/core/decoders/copyrect.js b/systemvm/agent/noVNC/core/decoders/copyrect.js
new file mode 100644
index 0000000..a78ded7
--- /dev/null
+++ b/systemvm/agent/noVNC/core/decoders/copyrect.js
@@ -0,0 +1,24 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+export default class CopyRectDecoder {
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (sock.rQwait("COPYRECT", 4)) {
+ return false;
+ }
+
+ let deltaX = sock.rQshift16();
+ let deltaY = sock.rQshift16();
+ display.copyImage(deltaX, deltaY, x, y, width, height);
+
+ return true;
+ }
+}
diff --git a/systemvm/agent/noVNC/core/decoders/hextile.js b/systemvm/agent/noVNC/core/decoders/hextile.js
new file mode 100644
index 0000000..aa76d2f
--- /dev/null
+++ b/systemvm/agent/noVNC/core/decoders/hextile.js
@@ -0,0 +1,139 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import * as Log from '../util/logging.js';
+
+export default class HextileDecoder {
+ constructor() {
+ this._tiles = 0;
+ this._lastsubencoding = 0;
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._tiles === 0) {
+ this._tiles_x = Math.ceil(width / 16);
+ this._tiles_y = Math.ceil(height / 16);
+ this._total_tiles = this._tiles_x * this._tiles_y;
+ this._tiles = this._total_tiles;
+ }
+
+ while (this._tiles > 0) {
+ let bytes = 1;
+
+ if (sock.rQwait("HEXTILE", bytes)) {
+ return false;
+ }
+
+ let rQ = sock.rQ;
+ let rQi = sock.rQi;
+
+ let subencoding = rQ[rQi]; // Peek
+ if (subencoding > 30) { // Raw
+ throw new Error("Illegal hextile subencoding (subencoding: " +
+ subencoding + ")");
+ }
+
+ const curr_tile = this._total_tiles - this._tiles;
+ const tile_x = curr_tile % this._tiles_x;
+ const tile_y = Math.floor(curr_tile / this._tiles_x);
+ const tx = x + tile_x * 16;
+ const ty = y + tile_y * 16;
+ const tw = Math.min(16, (x + width) - tx);
+ const th = Math.min(16, (y + height) - ty);
+
+ // Figure out how much we are expecting
+ if (subencoding & 0x01) { // Raw
+ bytes += tw * th * 4;
+ } else {
+ if (subencoding & 0x02) { // Background
+ bytes += 4;
+ }
+ if (subencoding & 0x04) { // Foreground
+ bytes += 4;
+ }
+ if (subencoding & 0x08) { // AnySubrects
+ bytes++; // Since we aren't shifting it off
+
+ if (sock.rQwait("HEXTILE", bytes)) {
+ return false;
+ }
+
+ let subrects = rQ[rQi + bytes - 1]; // Peek
+ if (subencoding & 0x10) { // SubrectsColoured
+ bytes += subrects * (4 + 2);
+ } else {
+ bytes += subrects * 2;
+ }
+ }
+ }
+
+ if (sock.rQwait("HEXTILE", bytes)) {
+ return false;
+ }
+
+ // We know the encoding and have a whole tile
+ rQi++;
+ if (subencoding === 0) {
+ if (this._lastsubencoding & 0x01) {
+ // Weird: ignore blanks are RAW
+ Log.Debug(" Ignoring blank after RAW");
+ } else {
+ display.fillRect(tx, ty, tw, th, this._background);
+ }
+ } else if (subencoding & 0x01) { // Raw
+ display.blitImage(tx, ty, tw, th, rQ, rQi);
+ rQi += bytes - 1;
+ } else {
+ if (subencoding & 0x02) { // Background
+ this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ rQi += 4;
+ }
+ if (subencoding & 0x04) { // Foreground
+ this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ rQi += 4;
+ }
+
+ display.startTile(tx, ty, tw, th, this._background);
+ if (subencoding & 0x08) { // AnySubrects
+ let subrects = rQ[rQi];
+ rQi++;
+
+ for (let s = 0; s < subrects; s++) {
+ let color;
+ if (subencoding & 0x10) { // SubrectsColoured
+ color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ rQi += 4;
+ } else {
+ color = this._foreground;
+ }
+ const xy = rQ[rQi];
+ rQi++;
+ const sx = (xy >> 4);
+ const sy = (xy & 0x0f);
+
+ const wh = rQ[rQi];
+ rQi++;
+ const sw = (wh >> 4) + 1;
+ const sh = (wh & 0x0f) + 1;
+
+ display.subTile(sx, sy, sw, sh, color);
+ }
+ }
+ display.finishTile();
+ }
+ sock.rQi = rQi;
+ this._lastsubencoding = subencoding;
+ this._tiles--;
+ }
+
+ return true;
+ }
+}
diff --git a/systemvm/agent/noVNC/core/decoders/raw.js b/systemvm/agent/noVNC/core/decoders/raw.js
new file mode 100644
index 0000000..f676e0d
--- /dev/null
+++ b/systemvm/agent/noVNC/core/decoders/raw.js
@@ -0,0 +1,58 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+export default class RawDecoder {
+ constructor() {
+ this._lines = 0;
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._lines === 0) {
+ this._lines = height;
+ }
+
+ const pixelSize = depth == 8 ? 1 : 4;
+ const bytesPerLine = width * pixelSize;
+
+ if (sock.rQwait("RAW", bytesPerLine)) {
+ return false;
+ }
+
+ const cur_y = y + (height - this._lines);
+ const curr_height = Math.min(this._lines,
+ Math.floor(sock.rQlen / bytesPerLine));
+ let data = sock.rQ;
+ let index = sock.rQi;
+
+ // Convert data if needed
+ if (depth == 8) {
+ const pixels = width * curr_height;
+ const newdata = new Uint8Array(pixels * 4);
+ for (let i = 0; i < pixels; i++) {
+ newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
+ newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
+ newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
+ newdata[i * 4 + 4] = 0;
+ }
+ data = newdata;
+ index = 0;
+ }
+
+ display.blitImage(x, cur_y, width, curr_height, data, index);
+ sock.rQskipBytes(curr_height * bytesPerLine);
+ this._lines -= curr_height;
+ if (this._lines > 0) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/systemvm/agent/noVNC/core/decoders/rre.js b/systemvm/agent/noVNC/core/decoders/rre.js
new file mode 100644
index 0000000..57414a0
--- /dev/null
+++ b/systemvm/agent/noVNC/core/decoders/rre.js
@@ -0,0 +1,46 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+export default class RREDecoder {
+ constructor() {
+ this._subrects = 0;
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._subrects === 0) {
+ if (sock.rQwait("RRE", 4 + 4)) {
+ return false;
+ }
+
+ this._subrects = sock.rQshift32();
+
+ let color = sock.rQshiftBytes(4); // Background
+ display.fillRect(x, y, width, height, color);
+ }
+
+ while (this._subrects > 0) {
+ if (sock.rQwait("RRE", 4 + 8)) {
+ return false;
+ }
+
+ let color = sock.rQshiftBytes(4);
+ let sx = sock.rQshift16();
+ let sy = sock.rQshift16();
+ let swidth = sock.rQshift16();
+ let sheight = sock.rQshift16();
+ display.fillRect(x + sx, y + sy, swidth, sheight, color);
+
+ this._subrects--;
+ }
+
+ return true;
+ }
+}
diff --git a/systemvm/agent/noVNC/core/decoders/tight.js b/systemvm/agent/noVNC/core/decoders/tight.js
new file mode 100644
index 0000000..bcda04c
--- /dev/null
+++ b/systemvm/agent/noVNC/core/decoders/tight.js
@@ -0,0 +1,319 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import * as Log from '../util/logging.js';
+import Inflator from "../inflator.js";
+
+export default class TightDecoder {
+ constructor() {
+ this._ctl = null;
+ this._filter = null;
+ this._numColors = 0;
+ this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
+ this._len = 0;
+
+ this._zlibs = [];
+ for (let i = 0; i < 4; i++) {
+ this._zlibs[i] = new Inflator();
+ }
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._ctl === null) {
+ if (sock.rQwait("TIGHT compression-control", 1)) {
+ return false;
+ }
+
+ this._ctl = sock.rQshift8();
+
+ // Reset streams if the server requests it
+ for (let i = 0; i < 4; i++) {
+ if ((this._ctl >> i) & 1) {
+ this._zlibs[i].reset();
+ Log.Info("Reset zlib stream " + i);
+ }
+ }
+
+ // Figure out filter
+ this._ctl = this._ctl >> 4;
+ }
+
+ let ret;
+
+ if (this._ctl === 0x08) {
+ ret = this._fillRect(x, y, width, height,
+ sock, display, depth);
+ } else if (this._ctl === 0x09) {
+ ret = this._jpegRect(x, y, width, height,
+ sock, display, depth);
+ } else if (this._ctl === 0x0A) {
+ ret = this._pngRect(x, y, width, height,
+ sock, display, depth);
+ } else if ((this._ctl & 0x80) == 0) {
+ ret = this._basicRect(this._ctl, x, y, width, height,
+ sock, display, depth);
+ } else {
+ throw new Error("Illegal tight compression received (ctl: " +
+ this._ctl + ")");
+ }
+
+ if (ret) {
+ this._ctl = null;
+ }
+
+ return ret;
+ }
+
+ _fillRect(x, y, width, height, sock, display, depth) {
+ if (sock.rQwait("TIGHT", 3)) {
+ return false;
+ }
+
+ const rQi = sock.rQi;
+ const rQ = sock.rQ;
+
+ display.fillRect(x, y, width, height,
+ [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
+ sock.rQskipBytes(3);
+
+ return true;
+ }
+
+ _jpegRect(x, y, width, height, sock, display, depth) {
+ let data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ display.imageRect(x, y, "image/jpeg", data);
+
+ return true;
+ }
+
+ _pngRect(x, y, width, height, sock, display, depth) {
+ throw new Error("PNG received in standard Tight rect");
+ }
+
+ _basicRect(ctl, x, y, width, height, sock, display, depth) {
+ if (this._filter === null) {
+ if (ctl & 0x4) {
+ if (sock.rQwait("TIGHT", 1)) {
+ return false;
+ }
+
+ this._filter = sock.rQshift8();
+ } else {
+ // Implicit CopyFilter
+ this._filter = 0;
+ }
+ }
+
+ let streamId = ctl & 0x3;
+
+ let ret;
+
+ switch (this._filter) {
+ case 0: // CopyFilter
+ ret = this._copyFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ case 1: // PaletteFilter
+ ret = this._paletteFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ case 2: // GradientFilter
+ ret = this._gradientFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ default:
+ throw new Error("Illegal tight filter received (ctl: " +
+ this._filter + ")");
+ }
+
+ if (ret) {
+ this._filter = null;
+ }
+
+ return ret;
+ }
+
+ _copyFilter(streamId, x, y, width, height, sock, display, depth) {
+ const uncompressedSize = width * height * 3;
+ let data;
+
+ if (uncompressedSize < 12) {
+ if (sock.rQwait("TIGHT", uncompressedSize)) {
+ return false;
+ }
+
+ data = sock.rQshiftBytes(uncompressedSize);
+ } else {
+ data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
+ if (data.length != uncompressedSize) {
+ throw new Error("Incomplete zlib block");
+ }
+ }
+
+ display.blitRgbImage(x, y, width, height, data, 0, false);
+
+ return true;
+ }
+
+ _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
+ if (this._numColors === 0) {
+ if (sock.rQwait("TIGHT palette", 1)) {
+ return false;
+ }
+
+ const numColors = sock.rQpeek8() + 1;
+ const paletteSize = numColors * 3;
+
+ if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
+ return false;
+ }
+
+ this._numColors = numColors;
+ sock.rQskipBytes(1);
+
+ sock.rQshiftTo(this._palette, paletteSize);
+ }
+
+ const bpp = (this._numColors <= 2) ? 1 : 8;
+ const rowSize = Math.floor((width * bpp + 7) / 8);
+ const uncompressedSize = rowSize * height;
+
+ let data;
+
+ if (uncompressedSize < 12) {
+ if (sock.rQwait("TIGHT", uncompressedSize)) {
+ return false;
+ }
+
+ data = sock.rQshiftBytes(uncompressedSize);
+ } else {
+ data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
+ if (data.length != uncompressedSize) {
+ throw new Error("Incomplete zlib block");
+ }
+ }
+
+ // Convert indexed (palette based) image data to RGB
+ if (this._numColors == 2) {
+ this._monoRect(x, y, width, height, data, this._palette, display);
+ } else {
+ this._paletteRect(x, y, width, height, data, this._palette, display);
+ }
+
+ this._numColors = 0;
+
+ return true;
+ }
+
+ _monoRect(x, y, width, height, data, palette, display) {
+ // Convert indexed (palette based) image data to RGB
+ // TODO: reduce number of calculations inside loop
+ const dest = this._getScratchBuffer(width * height * 4);
+ const w = Math.floor((width + 7) / 8);
+ const w1 = Math.floor(width / 8);
+
+ for (let y = 0; y < height; y++) {
+ let dp, sp, x;
+ for (x = 0; x < w1; x++) {
+ for (let b = 7; b >= 0; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 4;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ dest[dp + 3] = 255;
+ }
+ }
+
+ for (let b = 7; b >= 8 - width % 8; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 4;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ dest[dp + 3] = 255;
+ }
+ }
+
+ display.blitRgbxImage(x, y, width, height, dest, 0, false);
+ }
+
+ _paletteRect(x, y, width, height, data, palette, display) {
+ // Convert indexed (palette based) image data to RGB
+ const dest = this._getScratchBuffer(width * height * 4);
+ const total = width * height * 4;
+ for (let i = 0, j = 0; i < total; i += 4, j++) {
+ const sp = data[j] * 3;
+ dest[i] = palette[sp];
+ dest[i + 1] = palette[sp + 1];
+ dest[i + 2] = palette[sp + 2];
+ dest[i + 3] = 255;
+ }
+
+ display.blitRgbxImage(x, y, width, height, dest, 0, false);
+ }
+
+ _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
+ throw new Error("Gradient filter not implemented");
+ }
+
+ _readData(sock) {
+ if (this._len === 0) {
+ if (sock.rQwait("TIGHT", 3)) {
+ return null;
+ }
+
+ let byte;
+
+ byte = sock.rQshift8();
+ this._len = byte & 0x7f;
+ if (byte & 0x80) {
+ byte = sock.rQshift8();
+ this._len |= (byte & 0x7f) << 7;
+ if (byte & 0x80) {
+ byte = sock.rQshift8();
+ this._len |= byte << 14;
+ }
+ }
+ }
+
+ if (sock.rQwait("TIGHT", this._len)) {
+ return null;
+ }
+
+ let data = sock.rQshiftBytes(this._len);
+ this._len = 0;
+
+ return data;
+ }
+
+ _getScratchBuffer(size) {
+ if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
+ this._scratchBuffer = new Uint8Array(size);
+ }
+ return this._scratchBuffer;
+ }
+}
diff --git a/systemvm/agent/noVNC/core/decoders/tightpng.js b/systemvm/agent/noVNC/core/decoders/tightpng.js
new file mode 100644
index 0000000..7bbde3a
--- /dev/null
+++ b/systemvm/agent/noVNC/core/decoders/tightpng.js
@@ -0,0 +1,29 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import TightDecoder from './tight.js';
+
+export default class TightPNGDecoder extends TightDecoder {
+ _pngRect(x, y, width, height, sock, display, depth) {
+ let data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ display.imageRect(x, y, "image/png", data);
+
+ return true;
+ }
+
+ _basicRect(ctl, x, y, width, height, sock, display, depth) {
+ throw new Error("BasicCompression received in TightPNG rect");
+ }
+}
diff --git a/systemvm/agent/noVNC/core/des.js b/systemvm/agent/noVNC/core/des.js
new file mode 100644
index 0000000..d2f807b
--- /dev/null
+++ b/systemvm/agent/noVNC/core/des.js
@@ -0,0 +1,266 @@
+/*
+ * Ported from Flashlight VNC ActionScript implementation:
+ * http://www.wizhelp.com/flashlight-vnc/
+ *
+ * Full attribution follows:
+ *
+ * -------------------------------------------------------------------------
+ *
+ * This DES class has been extracted from package Acme.Crypto for use in VNC.
+ * The unnecessary odd parity code has been removed.
+ *
+ * These changes are:
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+
+ * DesCipher - the DES encryption method
+ *
+ * The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
+ *
+ * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+ * without fee is hereby granted, provided that this copyright notice is kept
+ * intact.
+ *
+ * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+ * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+ * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ *
+ * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+ * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+ * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+ * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+ * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+ * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
+ * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+ * HIGH RISK ACTIVITIES.
+ *
+ *
+ * The rest is:
+ *
+ * Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Visit the ACME Labs Java page for up-to-date versions of this and other
+ * fine Java utilities: http://www.acme.com/java/
+ */
+
+/* eslint-disable comma-spacing */
+
+// Tables, permutations, S-boxes, etc.
+const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
+ 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
+ 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
+ totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
+
+const z = 0x0;
+let a,b,c,d,e,f;
+a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
+const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
+ z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
+ a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
+ c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
+a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
+const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
+ a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
+ z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
+ z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
+a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
+const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
+ b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
+ c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
+ b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
+a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
+const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
+ z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
+ b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
+ c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
+a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
+const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
+ a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
+ z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
+ c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
+a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
+const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
+ z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
+ b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
+ a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
+a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
+const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
+ b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
+ b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
+ z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
+a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
+const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
+ c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
+ a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
+ z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
+
+/* eslint-enable comma-spacing */
+
+export default class DES {
+ constructor(password) {
+ this.keys = [];
+
+ // Set the key.
+ const pc1m = [], pcr = [], kn = [];
+
+ for (let j = 0, l = 56; j < 56; ++j, l -= 8) {
+ l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
+ const m = l & 0x7;
+ pc1m[j] = ((password[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
+ }
+
+ for (let i = 0; i < 16; ++i) {
+ const m = i << 1;
+ const n = m + 1;
+ kn[m] = kn[n] = 0;
+ for (let o = 28; o < 59; o += 28) {
+ for (let j = o - 28; j < o; ++j) {
+ const l = j + totrot[i];
+ pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];
+ }
+ }
+ for (let j = 0; j < 24; ++j) {
+ if (pcr[PC2[j]] !== 0) {
+ kn[m] |= 1 << (23 - j);
+ }
+ if (pcr[PC2[j + 24]] !== 0) {
+ kn[n] |= 1 << (23 - j);
+ }
+ }
+ }
+
+ // cookey
+ for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
+ const raw0 = kn[rawi++];
+ const raw1 = kn[rawi++];
+ this.keys[KnLi] = (raw0 & 0x00fc0000) << 6;
+ this.keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
+ this.keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
+ this.keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
+ ++KnLi;
+ this.keys[KnLi] = (raw0 & 0x0003f000) << 12;
+ this.keys[KnLi] |= (raw0 & 0x0000003f) << 16;
+ this.keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
+ this.keys[KnLi] |= (raw1 & 0x0000003f);
+ ++KnLi;
+ }
+ }
+
+ // Encrypt 8 bytes of text
+ enc8(text) {
+ const b = text.slice();
+ let i = 0, l, r, x; // left, right, accumulator
+
+ // Squash 8 bytes to 2 ints
+ l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+ r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+
+ x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
+ r ^= x;
+ l ^= (x << 4);
+ x = ((l >>> 16) ^ r) & 0x0000ffff;
+ r ^= x;
+ l ^= (x << 16);
+ x = ((r >>> 2) ^ l) & 0x33333333;
+ l ^= x;
+ r ^= (x << 2);
+ x = ((r >>> 8) ^ l) & 0x00ff00ff;
+ l ^= x;
+ r ^= (x << 8);
+ r = (r << 1) | ((r >>> 31) & 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 1) | ((l >>> 31) & 1);
+
+ for (let i = 0, keysi = 0; i < 8; ++i) {
+ x = (r << 28) | (r >>> 4);
+ x ^= this.keys[keysi++];
+ let fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = r ^ this.keys[keysi++];
+ fval |= SP8[x & 0x3f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ l ^= fval;
+ x = (l << 28) | (l >>> 4);
+ x ^= this.keys[keysi++];
+ fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = l ^ this.keys[keysi++];
+ fval |= SP8[x & 0x0000003f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ r ^= fval;
+ }
+
+ r = (r << 31) | (r >>> 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 31) | (l >>> 1);
+ x = ((l >>> 8) ^ r) & 0x00ff00ff;
+ r ^= x;
+ l ^= (x << 8);
+ x = ((l >>> 2) ^ r) & 0x33333333;
+ r ^= x;
+ l ^= (x << 2);
+ x = ((r >>> 16) ^ l) & 0x0000ffff;
+ l ^= x;
+ r ^= (x << 16);
+ x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
+ l ^= x;
+ r ^= (x << 4);
+
+ // Spread ints to bytes
+ x = [r, l];
+ for (i = 0; i < 8; i++) {
+ b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
+ if (b[i] < 0) { b[i] += 256; } // unsigned
+ }
+ return b;
+ }
+
+ // Encrypt 16 bytes of text using passwd as key
+ encrypt(t) {
+ return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16)));
+ }
+}
diff --git a/systemvm/agent/noVNC/core/display.js b/systemvm/agent/noVNC/core/display.js
new file mode 100644
index 0000000..1528384
--- /dev/null
+++ b/systemvm/agent/noVNC/core/display.js
@@ -0,0 +1,654 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+import * as Log from './util/logging.js';
+import Base64 from "./base64.js";
+import { supportsImageMetadata } from './util/browser.js';
+
+export default class Display {
+ constructor(target) {
+ this._drawCtx = null;
+ this._c_forceCanvas = false;
+
+ this._renderQ = []; // queue drawing actions for in-oder rendering
+ this._flushing = false;
+
+ // the full frame buffer (logical canvas) size
+ this._fb_width = 0;
+ this._fb_height = 0;
+
+ this._prevDrawStyle = "";
+ this._tile = null;
+ this._tile16x16 = null;
+ this._tile_x = 0;
+ this._tile_y = 0;
+
+ Log.Debug(">> Display.constructor");
+
+ // The visible canvas
+ this._target = target;
+
+ if (!this._target) {
+ throw new Error("Target must be set");
+ }
+
+ if (typeof this._target === 'string') {
+ throw new Error('target must be a DOM element');
+ }
+
+ if (!this._target.getContext) {
+ throw new Error("no getContext method");
+ }
+
+ this._targetCtx = this._target.getContext('2d');
+
+ // the visible canvas viewport (i.e. what actually gets seen)
+ this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
+
+ // The hidden canvas, where we do the actual rendering
+ this._backbuffer = document.createElement('canvas');
+ this._drawCtx = this._backbuffer.getContext('2d');
+
+ this._damageBounds = { left: 0, top: 0,
+ right: this._backbuffer.width,
+ bottom: this._backbuffer.height };
+
+ Log.Debug("User Agent: " + navigator.userAgent);
+
+ this.clear();
+
+ // Check canvas features
+ if (!('createImageData' in this._drawCtx)) {
+ throw new Error("Canvas does not support createImageData");
+ }
+
+ this._tile16x16 = this._drawCtx.createImageData(16, 16);
+ Log.Debug("<< Display.constructor");
+
+ // ===== PROPERTIES =====
+
+ this._scale = 1.0;
+ this._clipViewport = false;
+ this.logo = null;
+
+ // ===== EVENT HANDLERS =====
+
+ this.onflush = () => {}; // A flush request has finished
+ }
+
+ // ===== PROPERTIES =====
+
+ get scale() { return this._scale; }
+ set scale(scale) {
+ this._rescale(scale);
+ }
+
+ get clipViewport() { return this._clipViewport; }
+ set clipViewport(viewport) {
+ this._clipViewport = viewport;
+ // May need to readjust the viewport dimensions
+ const vp = this._viewportLoc;
+ this.viewportChangeSize(vp.w, vp.h);
+ this.viewportChangePos(0, 0);
+ }
+
+ get width() {
+ return this._fb_width;
+ }
+
+ get height() {
+ return this._fb_height;
+ }
+
+ // ===== PUBLIC METHODS =====
+
+ viewportChangePos(deltaX, deltaY) {
+ const vp = this._viewportLoc;
+ deltaX = Math.floor(deltaX);
+ deltaY = Math.floor(deltaY);
+
+ if (!this._clipViewport) {
+ deltaX = -vp.w; // clamped later of out of bounds
+ deltaY = -vp.h;
+ }
+
+ const vx2 = vp.x + vp.w - 1;
+ const vy2 = vp.y + vp.h - 1;
+
+ // Position change
+
+ if (deltaX < 0 && vp.x + deltaX < 0) {
+ deltaX = -vp.x;
+ }
+ if (vx2 + deltaX >= this._fb_width) {
+ deltaX -= vx2 + deltaX - this._fb_width + 1;
+ }
+
+ if (vp.y + deltaY < 0) {
+ deltaY = -vp.y;
+ }
+ if (vy2 + deltaY >= this._fb_height) {
+ deltaY -= (vy2 + deltaY - this._fb_height + 1);
+ }
+
+ if (deltaX === 0 && deltaY === 0) {
+ return;
+ }
+ Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
+
+ vp.x += deltaX;
+ vp.y += deltaY;
+
+ this._damage(vp.x, vp.y, vp.w, vp.h);
+
+ this.flip();
+ }
+
+ viewportChangeSize(width, height) {
+
+ if (!this._clipViewport ||
+ typeof(width) === "undefined" ||
+ typeof(height) === "undefined") {
+
+ Log.Debug("Setting viewport to full display region");
+ width = this._fb_width;
+ height = this._fb_height;
+ }
+
+ width = Math.floor(width);
+ height = Math.floor(height);
+
+ if (width > this._fb_width) {
+ width = this._fb_width;
+ }
+ if (height > this._fb_height) {
+ height = this._fb_height;
+ }
+
+ const vp = this._viewportLoc;
+ if (vp.w !== width || vp.h !== height) {
+ vp.w = width;
+ vp.h = height;
+
+ const canvas = this._target;
+ canvas.width = width;
+ canvas.height = height;
+
+ // The position might need to be updated if we've grown
+ this.viewportChangePos(0, 0);
+
+ this._damage(vp.x, vp.y, vp.w, vp.h);
+ this.flip();
+
+ // Update the visible size of the target canvas
+ this._rescale(this._scale);
+ }
+ }
+
+ absX(x) {
+ if (this._scale === 0) {
+ return 0;
+ }
+ return x / this._scale + this._viewportLoc.x;
+ }
+
+ absY(y) {
+ if (this._scale === 0) {
+ return 0;
+ }
+ return y / this._scale + this._viewportLoc.y;
+ }
+
+ resize(width, height) {
+ this._prevDrawStyle = "";
+
+ this._fb_width = width;
+ this._fb_height = height;
+
+ const canvas = this._backbuffer;
+ if (canvas.width !== width || canvas.height !== height) {
+
+ // We have to save the canvas data since changing the size will clear it
+ let saveImg = null;
+ if (canvas.width > 0 && canvas.height > 0) {
+ saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);
+ }
+
+ if (canvas.width !== width) {
+ canvas.width = width;
+ }
+ if (canvas.height !== height) {
+ canvas.height = height;
+ }
+
+ if (saveImg) {
+ this._drawCtx.putImageData(saveImg, 0, 0);
+ }
+ }
+
+ // Readjust the viewport as it may be incorrectly sized
+ // and positioned
+ const vp = this._viewportLoc;
+ this.viewportChangeSize(vp.w, vp.h);
+ this.viewportChangePos(0, 0);
+ }
+
+ // Track what parts of the visible canvas that need updating
+ _damage(x, y, w, h) {
+ if (x < this._damageBounds.left) {
+ this._damageBounds.left = x;
+ }
+ if (y < this._damageBounds.top) {
+ this._damageBounds.top = y;
+ }
+ if ((x + w) > this._damageBounds.right) {
+ this._damageBounds.right = x + w;
+ }
+ if ((y + h) > this._damageBounds.bottom) {
+ this._damageBounds.bottom = y + h;
+ }
+ }
+
+ // Update the visible canvas with the contents of the
+ // rendering canvas
+ flip(from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ this._renderQ_push({
+ 'type': 'flip'
+ });
+ } else {
+ let x = this._damageBounds.left;
+ let y = this._damageBounds.top;
+ let w = this._damageBounds.right - x;
+ let h = this._damageBounds.bottom - y;
+
+ let vx = x - this._viewportLoc.x;
+ let vy = y - this._viewportLoc.y;
+
+ if (vx < 0) {
+ w += vx;
+ x -= vx;
+ vx = 0;
+ }
+ if (vy < 0) {
+ h += vy;
+ y -= vy;
+ vy = 0;
+ }
+
+ if ((vx + w) > this._viewportLoc.w) {
+ w = this._viewportLoc.w - vx;
+ }
+ if ((vy + h) > this._viewportLoc.h) {
+ h = this._viewportLoc.h - vy;
+ }
+
+ if ((w > 0) && (h > 0)) {
+ // FIXME: We may need to disable image smoothing here
+ // as well (see copyImage()), but we haven't
+ // noticed any problem yet.
+ this._targetCtx.drawImage(this._backbuffer,
+ x, y, w, h,
+ vx, vy, w, h);
+ }
+
+ this._damageBounds.left = this._damageBounds.top = 65535;
+ this._damageBounds.right = this._damageBounds.bottom = 0;
+ }
+ }
+
+ clear() {
+ if (this._logo) {
+ this.resize(this._logo.width, this._logo.height);
+ this.imageRect(0, 0, this._logo.type, this._logo.data);
+ } else {
+ this.resize(240, 20);
+ this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
+ }
+ this.flip();
+ }
+
+ pending() {
+ return this._renderQ.length > 0;
+ }
+
+ flush() {
+ if (this._renderQ.length === 0) {
+ this.onflush();
+ } else {
+ this._flushing = true;
+ }
+ }
+
+ fillRect(x, y, width, height, color, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ this._renderQ_push({
+ 'type': 'fill',
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ 'color': color
+ });
+ } else {
+ this._setFillColor(color);
+ this._drawCtx.fillRect(x, y, width, height);
+ this._damage(x, y, width, height);
+ }
+ }
+
+ copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ this._renderQ_push({
+ 'type': 'copy',
+ 'old_x': old_x,
+ 'old_y': old_y,
+ 'x': new_x,
+ 'y': new_y,
+ 'width': w,
+ 'height': h,
+ });
+ } else {
+ // Due to this bug among others [1] we need to disable the image-smoothing to
+ // avoid getting a blur effect when copying data.
+ //
+ // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
+ //
+ // We need to set these every time since all properties are reset
+ // when the the size is changed
+ this._drawCtx.mozImageSmoothingEnabled = false;
+ this._drawCtx.webkitImageSmoothingEnabled = false;
+ this._drawCtx.msImageSmoothingEnabled = false;
+ this._drawCtx.imageSmoothingEnabled = false;
+
+ this._drawCtx.drawImage(this._backbuffer,
+ old_x, old_y, w, h,
+ new_x, new_y, w, h);
+ this._damage(new_x, new_y, w, h);
+ }
+ }
+
+ imageRect(x, y, mime, arr) {
+ const img = new Image();
+ img.src = "data: " + mime + ";base64," + Base64.encode(arr);
+ this._renderQ_push({
+ 'type': 'img',
+ 'img': img,
+ 'x': x,
+ 'y': y
+ });
+ }
+
+ // start updating a tile
+ startTile(x, y, width, height, color) {
+ this._tile_x = x;
+ this._tile_y = y;
+ if (width === 16 && height === 16) {
+ this._tile = this._tile16x16;
+ } else {
+ this._tile = this._drawCtx.createImageData(width, height);
+ }
+
+ const red = color[2];
+ const green = color[1];
+ const blue = color[0];
+
+ const data = this._tile.data;
+ for (let i = 0; i < width * height * 4; i += 4) {
+ data[i] = red;
+ data[i + 1] = green;
+ data[i + 2] = blue;
+ data[i + 3] = 255;
+ }
+ }
+
+ // update sub-rectangle of the current tile
+ subTile(x, y, w, h, color) {
+ const red = color[2];
+ const green = color[1];
+ const blue = color[0];
+ const xend = x + w;
+ const yend = y + h;
+
+ const data = this._tile.data;
+ const width = this._tile.width;
+ for (let j = y; j < yend; j++) {
+ for (let i = x; i < xend; i++) {
+ const p = (i + (j * width)) * 4;
+ data[p] = red;
+ data[p + 1] = green;
+ data[p + 2] = blue;
+ data[p + 3] = 255;
+ }
+ }
+ }
+
+ // draw the current tile to the screen
+ finishTile() {
+ this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
+ this._damage(this._tile_x, this._tile_y,
+ this._tile.width, this._tile.height);
+ }
+
+ blitImage(x, y, width, height, arr, offset, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ // NB(directxman12): it's technically more performant here to use preallocated arrays,
+ // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+ // this probably isn't getting called *nearly* as much
+ const new_arr = new Uint8Array(width * height * 4);
+ new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+ this._renderQ_push({
+ 'type': 'blit',
+ 'data': new_arr,
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ });
+ } else {
+ this._bgrxImageData(x, y, width, height, arr, offset);
+ }
+ }
+
+ blitRgbImage(x, y, width, height, arr, offset, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ // NB(directxman12): it's technically more performant here to use preallocated arrays,
+ // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+ // this probably isn't getting called *nearly* as much
+ const new_arr = new Uint8Array(width * height * 3);
+ new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+ this._renderQ_push({
+ 'type': 'blitRgb',
+ 'data': new_arr,
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ });
+ } else {
+ this._rgbImageData(x, y, width, height, arr, offset);
+ }
+ }
+
+ blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ // NB(directxman12): it's technically more performant here to use preallocated arrays,
+ // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+ // this probably isn't getting called *nearly* as much
+ const new_arr = new Uint8Array(width * height * 4);
+ new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+ this._renderQ_push({
+ 'type': 'blitRgbx',
+ 'data': new_arr,
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ });
+ } else {
+ this._rgbxImageData(x, y, width, height, arr, offset);
+ }
+ }
+
+ drawImage(img, x, y) {
+ this._drawCtx.drawImage(img, x, y);
+ this._damage(x, y, img.width, img.height);
+ }
+
+ autoscale(containerWidth, containerHeight) {
+ let scaleRatio;
+
+ if (containerWidth === 0 || containerHeight === 0) {
+ scaleRatio = 0;
+
+ } else {
+
+ const vp = this._viewportLoc;
+ const targetAspectRatio = containerWidth / containerHeight;
+ const fbAspectRatio = vp.w / vp.h;
+
+ if (fbAspectRatio >= targetAspectRatio) {
+ scaleRatio = containerWidth / vp.w;
+ } else {
+ scaleRatio = containerHeight / vp.h;
+ }
+ }
+
+ this._rescale(scaleRatio);
+ }
+
+ // ===== PRIVATE METHODS =====
+
+ _rescale(factor) {
+ this._scale = factor;
+ const vp = this._viewportLoc;
+
+ // NB(directxman12): If you set the width directly, or set the
+ // style width to a number, the canvas is cleared.
+ // However, if you set the style width to a string
+ // ('NNNpx'), the canvas is scaled without clearing.
+ const width = factor * vp.w + 'px';
+ const height = factor * vp.h + 'px';
+
+ if ((this._target.style.width !== width) ||
+ (this._target.style.height !== height)) {
+ this._target.style.width = width;
+ this._target.style.height = height;
+ }
+ }
+
+ _setFillColor(color) {
+ const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
+ if (newStyle !== this._prevDrawStyle) {
+ this._drawCtx.fillStyle = newStyle;
+ this._prevDrawStyle = newStyle;
+ }
+ }
+
+ _rgbImageData(x, y, width, height, arr, offset) {
+ const img = this._drawCtx.createImageData(width, height);
+ const data = img.data;
+ for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
+ data[i] = arr[j];
+ data[i + 1] = arr[j + 1];
+ data[i + 2] = arr[j + 2];
+ data[i + 3] = 255; // Alpha
+ }
+ this._drawCtx.putImageData(img, x, y);
+ this._damage(x, y, img.width, img.height);
+ }
+
+ _bgrxImageData(x, y, width, height, arr, offset) {
+ const img = this._drawCtx.createImageData(width, height);
+ const data = img.data;
+ for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
+ data[i] = arr[j + 2];
+ data[i + 1] = arr[j + 1];
+ data[i + 2] = arr[j];
+ data[i + 3] = 255; // Alpha
+ }
+ this._drawCtx.putImageData(img, x, y);
+ this._damage(x, y, img.width, img.height);
+ }
+
+ _rgbxImageData(x, y, width, height, arr, offset) {
+ // NB(directxman12): arr must be an Type Array view
+ let img;
+ if (supportsImageMetadata) {
+ img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
+ } else {
+ img = this._drawCtx.createImageData(width, height);
+ img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
+ }
+ this._drawCtx.putImageData(img, x, y);
+ this._damage(x, y, img.width, img.height);
+ }
+
+ _renderQ_push(action) {
+ this._renderQ.push(action);
+ if (this._renderQ.length === 1) {
+ // If this can be rendered immediately it will be, otherwise
+ // the scanner will wait for the relevant event
+ this._scan_renderQ();
+ }
+ }
+
+ _resume_renderQ() {
+ // "this" is the object that is ready, not the
+ // display object
+ this.removeEventListener('load', this._noVNC_display._resume_renderQ);
+ this._noVNC_display._scan_renderQ();
+ }
+
+ _scan_renderQ() {
+ let ready = true;
+ while (ready && this._renderQ.length > 0) {
+ const a = this._renderQ[0];
+ switch (a.type) {
+ case 'flip':
+ this.flip(true);
+ break;
+ case 'copy':
+ this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
+ break;
+ case 'fill':
+ this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
+ break;
+ case 'blit':
+ this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+ break;
+ case 'blitRgb':
+ this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+ break;
+ case 'blitRgbx':
+ this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+ break;
+ case 'img':
+ if (a.img.complete) {
+ this.drawImage(a.img, a.x, a.y);
+ } else {
+ a.img._noVNC_display = this;
+ a.img.addEventListener('load', this._resume_renderQ);
+ // We need to wait for this image to 'load'
+ // to keep things in-order
+ ready = false;
+ }
+ break;
+ }
+
+ if (ready) {
+ this._renderQ.shift();
+ }
+ }
+
+ if (this._renderQ.length === 0 && this._flushing) {
+ this._flushing = false;
+ this.onflush();
+ }
+ }
+}
diff --git a/systemvm/agent/noVNC/core/encodings.js b/systemvm/agent/noVNC/core/encodings.js
new file mode 100644
index 0000000..9fd38d5
--- /dev/null
+++ b/systemvm/agent/noVNC/core/encodings.js
@@ -0,0 +1,41 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+export const encodings = {
+ encodingRaw: 0,
+ encodingCopyRect: 1,
+ encodingRRE: 2,
+ encodingHextile: 5,
+ encodingTight: 7,
+ encodingTightPNG: -260,
+
+ pseudoEncodingQualityLevel9: -23,
+ pseudoEncodingQualityLevel0: -32,
+ pseudoEncodingDesktopSize: -223,
+ pseudoEncodingLastRect: -224,
+ pseudoEncodingCursor: -239,
+ pseudoEncodingQEMUExtendedKeyEvent: -258,
+ pseudoEncodingExtendedDesktopSize: -308,
+ pseudoEncodingXvp: -309,
+ pseudoEncodingFence: -312,
+ pseudoEncodingContinuousUpdates: -313,
+ pseudoEncodingCompressLevel9: -247,
+ pseudoEncodingCompressLevel0: -256,
+};
+
+export function encodingName(num) {
+ switch (num) {
+ case encodings.encodingRaw: return "Raw";
+ case encodings.encodingCopyRect: return "CopyRect";
+ case encodings.encodingRRE: return "RRE";
+ case encodings.encodingHextile: return "Hextile";
+ case encodings.encodingTight: return "Tight";
+ case encodings.encodingTightPNG: return "TightPNG";
+ default: return "[unknown encoding " + num + "]";
+ }
+}
diff --git a/systemvm/agent/noVNC/core/inflator.js b/systemvm/agent/noVNC/core/inflator.js
new file mode 100644
index 0000000..0eab8fe
--- /dev/null
+++ b/systemvm/agent/noVNC/core/inflator.js
@@ -0,0 +1,38 @@
+import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
+import ZStream from "../vendor/pako/lib/zlib/zstream.js";
+
+export default class Inflate {
+ constructor() {
+ this.strm = new ZStream();
+ this.chunkSize = 1024 * 10 * 10;
+ this.strm.output = new Uint8Array(this.chunkSize);
+ this.windowBits = 5;
+
+ inflateInit(this.strm, this.windowBits);
+ }
+
+ inflate(data, flush, expected) {
+ this.strm.input = data;
+ this.strm.avail_in = this.strm.input.length;
+ this.strm.next_in = 0;
+ this.strm.next_out = 0;
+
+ // resize our output buffer if it's too small
+ // (we could just use multiple chunks, but that would cause an extra
+ // allocation each time to flatten the chunks)
+ if (expected > this.chunkSize) {
+ this.chunkSize = expected;
+ this.strm.output = new Uint8Array(this.chunkSize);
+ }
+
+ this.strm.avail_out = this.chunkSize;
+
+ inflate(this.strm, flush);
+
+ return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
+ }
+
+ reset() {
+ inflateReset(this.strm);
+ }
+}
diff --git a/systemvm/agent/noVNC/core/input/domkeytable.js b/systemvm/agent/noVNC/core/input/domkeytable.js
new file mode 100644
index 0000000..60ae3f9
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/domkeytable.js
@@ -0,0 +1,307 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+import KeyTable from "./keysym.js";
+
+/*
+ * Mapping between HTML key values and VNC/X11 keysyms for "special"
+ * keys that cannot be handled via their Unicode codepoint.
+ *
+ * See https://www.w3.org/TR/uievents-key/ for possible values.
+ */
+
+const DOMKeyTable = {};
+
+function addStandard(key, standard) {
+ if (standard === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
+ if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\"");
+ DOMKeyTable[key] = [standard, standard, standard, standard];
+}
+
+function addLeftRight(key, left, right) {
+ if (left === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
+ if (right === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
+ if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\"");
+ DOMKeyTable[key] = [left, left, right, left];
+}
+
+function addNumpad(key, standard, numpad) {
+ if (standard === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
+ if (numpad === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
+ if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\"");
+ DOMKeyTable[key] = [standard, standard, standard, numpad];
+}
+
+// 2.2. Modifier Keys
+
+addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
+addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
+addStandard("CapsLock", KeyTable.XK_Caps_Lock);
+addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
+// - Fn
+// - FnLock
+addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+addStandard("NumLock", KeyTable.XK_Num_Lock);
+addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
+addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
+addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+// - Symbol
+// - SymbolLock
+
+// 2.3. Whitespace Keys
+
+addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
+addStandard("Tab", KeyTable.XK_Tab);
+addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space);
+
+// 2.4. Navigation Keys
+
+addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down);
+addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
+addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
+addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right);
+addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End);
+addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
+addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
+addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
+
+// 2.5. Editing Keys
+
+addStandard("Backspace", KeyTable.XK_BackSpace);
+addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
+addStandard("Copy", KeyTable.XF86XK_Copy);
+// - CrSel
+addStandard("Cut", KeyTable.XF86XK_Cut);
+addNumpad("Delete", KeyTable.XK_Delete, KeyTable.XK_KP_Delete);
+// - EraseEof
+// - ExSel
+addNumpad("Insert", KeyTable.XK_Insert, KeyTable.XK_KP_Insert);
+addStandard("Paste", KeyTable.XF86XK_Paste);
+addStandard("Redo", KeyTable.XK_Redo);
+addStandard("Undo", KeyTable.XK_Undo);
+
+// 2.6. UI Keys
+
+// - Accept
+// - Again (could just be XK_Redo)
+// - Attn
+addStandard("Cancel", KeyTable.XK_Cancel);
+addStandard("ContextMenu", KeyTable.XK_Menu);
+addStandard("Escape", KeyTable.XK_Escape);
+addStandard("Execute", KeyTable.XK_Execute);
+addStandard("Find", KeyTable.XK_Find);
+addStandard("Help", KeyTable.XK_Help);
+addStandard("Pause", KeyTable.XK_Pause);
+// - Play
+// - Props
+addStandard("Select", KeyTable.XK_Select);
+addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
+addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
+
+// 2.7. Device Keys
+
+addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
+addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
+addStandard("Eject", KeyTable.XF86XK_Eject);
+addStandard("LogOff", KeyTable.XF86XK_LogOff);
+addStandard("Power", KeyTable.XF86XK_PowerOff);
+addStandard("PowerOff", KeyTable.XF86XK_PowerDown);
+addStandard("PrintScreen", KeyTable.XK_Print);
+addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
+addStandard("Standby", KeyTable.XF86XK_Standby);
+addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
+
+// 2.8. IME and Composition Keys
+
+addStandard("AllCandidates", KeyTable.XK_MultipleCandidate);
+addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle
+addStandard("CodeInput", KeyTable.XK_Codeinput);
+addStandard("Compose", KeyTable.XK_Multi_key);
+addStandard("Convert", KeyTable.XK_Henkan);
+// - Dead
+// - FinalMode
+addStandard("GroupFirst", KeyTable.XK_ISO_First_Group);
+addStandard("GroupLast", KeyTable.XK_ISO_Last_Group);
+addStandard("GroupNext", KeyTable.XK_ISO_Next_Group);
+addStandard("GroupPrevious", KeyTable.XK_ISO_Prev_Group);
+// - ModeChange (XK_Mode_switch is often used for AltGr)
+// - NextCandidate
+addStandard("NonConvert", KeyTable.XK_Muhenkan);
+addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
+// - Process
+addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
+addStandard("HangulMode", KeyTable.XK_Hangul);
+addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja);
+addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja);
+addStandard("Eisu", KeyTable.XK_Eisu_toggle);
+addStandard("Hankaku", KeyTable.XK_Hankaku);
+addStandard("Hiragana", KeyTable.XK_Hiragana);
+addStandard("HiraganaKatakana", KeyTable.XK_Hiragana_Katakana);
+addStandard("KanaMode", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock
+addStandard("KanjiMode", KeyTable.XK_Kanji);
+addStandard("Katakana", KeyTable.XK_Katakana);
+addStandard("Romaji", KeyTable.XK_Romaji);
+addStandard("Zenkaku", KeyTable.XK_Zenkaku);
+addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku);
+
+// 2.9. General-Purpose Function Keys
+
+addStandard("F1", KeyTable.XK_F1);
+addStandard("F2", KeyTable.XK_F2);
+addStandard("F3", KeyTable.XK_F3);
+addStandard("F4", KeyTable.XK_F4);
+addStandard("F5", KeyTable.XK_F5);
+addStandard("F6", KeyTable.XK_F6);
+addStandard("F7", KeyTable.XK_F7);
+addStandard("F8", KeyTable.XK_F8);
+addStandard("F9", KeyTable.XK_F9);
+addStandard("F10", KeyTable.XK_F10);
+addStandard("F11", KeyTable.XK_F11);
+addStandard("F12", KeyTable.XK_F12);
+addStandard("F13", KeyTable.XK_F13);
+addStandard("F14", KeyTable.XK_F14);
+addStandard("F15", KeyTable.XK_F15);
+addStandard("F16", KeyTable.XK_F16);
+addStandard("F17", KeyTable.XK_F17);
+addStandard("F18", KeyTable.XK_F18);
+addStandard("F19", KeyTable.XK_F19);
+addStandard("F20", KeyTable.XK_F20);
+addStandard("F21", KeyTable.XK_F21);
+addStandard("F22", KeyTable.XK_F22);
+addStandard("F23", KeyTable.XK_F23);
+addStandard("F24", KeyTable.XK_F24);
+addStandard("F25", KeyTable.XK_F25);
+addStandard("F26", KeyTable.XK_F26);
+addStandard("F27", KeyTable.XK_F27);
+addStandard("F28", KeyTable.XK_F28);
+addStandard("F29", KeyTable.XK_F29);
+addStandard("F30", KeyTable.XK_F30);
+addStandard("F31", KeyTable.XK_F31);
+addStandard("F32", KeyTable.XK_F32);
+addStandard("F33", KeyTable.XK_F33);
+addStandard("F34", KeyTable.XK_F34);
+addStandard("F35", KeyTable.XK_F35);
+// - Soft1...
+
+// 2.10. Multimedia Keys
+
+// - ChannelDown
+// - ChannelUp
+addStandard("Close", KeyTable.XF86XK_Close);
+addStandard("MailForward", KeyTable.XF86XK_MailForward);
+addStandard("MailReply", KeyTable.XF86XK_Reply);
+addStandard("MainSend", KeyTable.XF86XK_Send);
+addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
+addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
+addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
+addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
+addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
+addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
+addStandard("MediaTrackNext", KeyTable.XF86XK_AudioNext);
+addStandard("MediaTrackPrevious", KeyTable.XF86XK_AudioPrev);
+addStandard("New", KeyTable.XF86XK_New);
+addStandard("Open", KeyTable.XF86XK_Open);
+addStandard("Print", KeyTable.XK_Print);
+addStandard("Save", KeyTable.XF86XK_Save);
+addStandard("SpellCheck", KeyTable.XF86XK_Spell);
+
+// 2.11. Multimedia Numpad Keys
+
+// - Key11
+// - Key12
+
+// 2.12. Audio Keys
+
+// - AudioBalanceLeft
+// - AudioBalanceRight
+// - AudioBassDown
+// - AudioBassBoostDown
+// - AudioBassBoostToggle
+// - AudioBassBoostUp
+// - AudioBassUp
+// - AudioFaderFront
+// - AudioFaderRear
+// - AudioSurroundModeNext
+// - AudioTrebleDown
+// - AudioTrebleUp
+addStandard("AudioVolumeDown", KeyTable.XF86XK_AudioLowerVolume);
+addStandard("AudioVolumeUp", KeyTable.XF86XK_AudioRaiseVolume);
+addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
+// - MicrophoneToggle
+// - MicrophoneVolumeDown
+// - MicrophoneVolumeUp
+addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
+
+// 2.13. Speech Keys
+
+// - SpeechCorrectionList
+// - SpeechInputToggle
+
+// 2.14. Application Keys
+
+addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
+addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
+addStandard("LaunchMail", KeyTable.XF86XK_Mail);
+addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
+addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
+addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
+addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
+addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
+addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
+addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
+addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
+addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
+
+// 2.15. Browser Keys
+
+addStandard("BrowserBack", KeyTable.XF86XK_Back);
+addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
+addStandard("BrowserForward", KeyTable.XF86XK_Forward);
+addStandard("BrowserHome", KeyTable.XF86XK_HomePage);
+addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
+addStandard("BrowserSearch", KeyTable.XF86XK_Search);
+addStandard("BrowserStop", KeyTable.XF86XK_Stop);
+
+// 2.16. Mobile Phone Keys
+
+// - A whole bunch...
+
+// 2.17. TV Keys
+
+// - A whole bunch...
+
+// 2.18. Media Controller Keys
+
+// - A whole bunch...
+addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);
+addStandard("MediaAudioTrack", KeyTable.XF86XK_AudioCycleTrack);
+addStandard("RandomToggle", KeyTable.XF86XK_AudioRandomPlay);
+addStandard("SplitScreenToggle", KeyTable.XF86XK_SplitScreen);
+addStandard("Subtitle", KeyTable.XF86XK_Subtitle);
+addStandard("VideoModeNext", KeyTable.XF86XK_Next_VMode);
+
+// Extra: Numpad
+
+addNumpad("=", KeyTable.XK_equal, KeyTable.XK_KP_Equal);
+addNumpad("+", KeyTable.XK_plus, KeyTable.XK_KP_Add);
+addNumpad("-", KeyTable.XK_minus, KeyTable.XK_KP_Subtract);
+addNumpad("*", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply);
+addNumpad("/", KeyTable.XK_slash, KeyTable.XK_KP_Divide);
+addNumpad(".", KeyTable.XK_period, KeyTable.XK_KP_Decimal);
+addNumpad(",", KeyTable.XK_comma, KeyTable.XK_KP_Separator);
+addNumpad("0", KeyTable.XK_0, KeyTable.XK_KP_0);
+addNumpad("1", KeyTable.XK_1, KeyTable.XK_KP_1);
+addNumpad("2", KeyTable.XK_2, KeyTable.XK_KP_2);
+addNumpad("3", KeyTable.XK_3, KeyTable.XK_KP_3);
+addNumpad("4", KeyTable.XK_4, KeyTable.XK_KP_4);
+addNumpad("5", KeyTable.XK_5, KeyTable.XK_KP_5);
+addNumpad("6", KeyTable.XK_6, KeyTable.XK_KP_6);
+addNumpad("7", KeyTable.XK_7, KeyTable.XK_KP_7);
+addNumpad("8", KeyTable.XK_8, KeyTable.XK_KP_8);
+addNumpad("9", KeyTable.XK_9, KeyTable.XK_KP_9);
+
+export default DOMKeyTable;
diff --git a/systemvm/agent/noVNC/core/input/fixedkeys.js b/systemvm/agent/noVNC/core/input/fixedkeys.js
new file mode 100644
index 0000000..4d09f2f
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/fixedkeys.js
@@ -0,0 +1,129 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*
+ * Fallback mapping between HTML key codes (physical keys) and
+ * HTML key values. This only works for keys that don't vary
+ * between layouts. We also omit those who manage fine by mapping the
+ * Unicode representation.
+ *
+ * See https://www.w3.org/TR/uievents-code/ for possible codes.
+ * See https://www.w3.org/TR/uievents-key/ for possible values.
+ */
+
+/* eslint-disable key-spacing */
+
+export default {
+
+// 3.1.1.1. Writing System Keys
+
+ 'Backspace': 'Backspace',
+
+// 3.1.1.2. Functional Keys
+
+ 'AltLeft': 'Alt',
+ 'AltRight': 'Alt', // This could also be 'AltGraph'
+ 'CapsLock': 'CapsLock',
+ 'ContextMenu': 'ContextMenu',
+ 'ControlLeft': 'Control',
+ 'ControlRight': 'Control',
+ 'Enter': 'Enter',
+ 'MetaLeft': 'Meta',
+ 'MetaRight': 'Meta',
+ 'ShiftLeft': 'Shift',
+ 'ShiftRight': 'Shift',
+ 'Tab': 'Tab',
+ // FIXME: Japanese/Korean keys
+
+// 3.1.2. Control Pad Section
+
+ 'Delete': 'Delete',
+ 'End': 'End',
+ 'Help': 'Help',
+ 'Home': 'Home',
+ 'Insert': 'Insert',
+ 'PageDown': 'PageDown',
+ 'PageUp': 'PageUp',
+
+// 3.1.3. Arrow Pad Section
+
+ 'ArrowDown': 'ArrowDown',
+ 'ArrowLeft': 'ArrowLeft',
+ 'ArrowRight': 'ArrowRight',
+ 'ArrowUp': 'ArrowUp',
+
+// 3.1.4. Numpad Section
+
+ 'NumLock': 'NumLock',
+ 'NumpadBackspace': 'Backspace',
+ 'NumpadClear': 'Clear',
+
+// 3.1.5. Function Section
+
+ 'Escape': 'Escape',
+ 'F1': 'F1',
+ 'F2': 'F2',
+ 'F3': 'F3',
+ 'F4': 'F4',
+ 'F5': 'F5',
+ 'F6': 'F6',
+ 'F7': 'F7',
+ 'F8': 'F8',
+ 'F9': 'F9',
+ 'F10': 'F10',
+ 'F11': 'F11',
+ 'F12': 'F12',
+ 'F13': 'F13',
+ 'F14': 'F14',
+ 'F15': 'F15',
+ 'F16': 'F16',
+ 'F17': 'F17',
+ 'F18': 'F18',
+ 'F19': 'F19',
+ 'F20': 'F20',
+ 'F21': 'F21',
+ 'F22': 'F22',
+ 'F23': 'F23',
+ 'F24': 'F24',
+ 'F25': 'F25',
+ 'F26': 'F26',
+ 'F27': 'F27',
+ 'F28': 'F28',
+ 'F29': 'F29',
+ 'F30': 'F30',
+ 'F31': 'F31',
+ 'F32': 'F32',
+ 'F33': 'F33',
+ 'F34': 'F34',
+ 'F35': 'F35',
+ 'PrintScreen': 'PrintScreen',
+ 'ScrollLock': 'ScrollLock',
+ 'Pause': 'Pause',
+
+// 3.1.6. Media Keys
+
+ 'BrowserBack': 'BrowserBack',
+ 'BrowserFavorites': 'BrowserFavorites',
+ 'BrowserForward': 'BrowserForward',
+ 'BrowserHome': 'BrowserHome',
+ 'BrowserRefresh': 'BrowserRefresh',
+ 'BrowserSearch': 'BrowserSearch',
+ 'BrowserStop': 'BrowserStop',
+ 'Eject': 'Eject',
+ 'LaunchApp1': 'LaunchMyComputer',
+ 'LaunchApp2': 'LaunchCalendar',
+ 'LaunchMail': 'LaunchMail',
+ 'MediaPlayPause': 'MediaPlay',
+ 'MediaStop': 'MediaStop',
+ 'MediaTrackNext': 'MediaTrackNext',
+ 'MediaTrackPrevious': 'MediaTrackPrevious',
+ 'Power': 'Power',
+ 'Sleep': 'Sleep',
+ 'AudioVolumeDown': 'AudioVolumeDown',
+ 'AudioVolumeMute': 'AudioVolumeMute',
+ 'AudioVolumeUp': 'AudioVolumeUp',
+ 'WakeUp': 'WakeUp',
+};
diff --git a/systemvm/agent/noVNC/core/input/keyboard.js b/systemvm/agent/noVNC/core/input/keyboard.js
new file mode 100644
index 0000000..9dbc8d6
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/keyboard.js
@@ -0,0 +1,370 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+import * as Log from '../util/logging.js';
+import { stopEvent } from '../util/events.js';
+import * as KeyboardUtil from "./util.js";
+import KeyTable from "./keysym.js";
+import * as browser from "../util/browser.js";
+
+//
+// Keyboard event handler
+//
+
+export default class Keyboard {
+ constructor(target) {
+ this._target = target || null;
+
+ this._keyDownList = {}; // List of depressed keys
+ // (even if they are happy)
+ this._pendingKey = null; // Key waiting for keypress
+ this._altGrArmed = false; // Windows AltGr detection
+
+ // keep these here so we can refer to them later
+ this._eventHandlers = {
+ 'keyup': this._handleKeyUp.bind(this),
+ 'keydown': this._handleKeyDown.bind(this),
+ 'keypress': this._handleKeyPress.bind(this),
+ 'blur': this._allKeysUp.bind(this),
+ 'checkalt': this._checkAlt.bind(this),
+ };
+
+ // ===== EVENT HANDLERS =====
+
+ this.onkeyevent = () => {}; // Handler for key press/release
+ }
+
+ // ===== PRIVATE METHODS =====
+
+ _sendKeyEvent(keysym, code, down) {
+ if (down) {
+ this._keyDownList[code] = keysym;
+ } else {
+ // Do we really think this key is down?
+ if (!(code in this._keyDownList)) {
+ return;
+ }
+ delete this._keyDownList[code];
+ }
+
+ Log.Debug("onkeyevent " + (down ? "down" : "up") +
+ ", keysym: " + keysym, ", code: " + code);
+ this.onkeyevent(keysym, code, down);
+ }
+
+ _getKeyCode(e) {
+ const code = KeyboardUtil.getKeycode(e);
+ if (code !== 'Unidentified') {
+ return code;
+ }
+
+ // Unstable, but we don't have anything else to go on
+ // (don't use it for 'keypress' events thought since
+ // WebKit sets it to the same as charCode)
+ if (e.keyCode && (e.type !== 'keypress')) {
+ // 229 is used for composition events
+ if (e.keyCode !== 229) {
+ return 'Platform' + e.keyCode;
+ }
+ }
+
+ // A precursor to the final DOM3 standard. Unfortunately it
+ // is not layout independent, so it is as bad as using keyCode
+ if (e.keyIdentifier) {
+ // Non-character key?
+ if (e.keyIdentifier.substr(0, 2) !== 'U+') {
+ return e.keyIdentifier;
+ }
+
+ const codepoint = parseInt(e.keyIdentifier.substr(2), 16);
+ const char = String.fromCharCode(codepoint).toUpperCase();
+
+ return 'Platform' + char.charCodeAt();
+ }
+
+ return 'Unidentified';
+ }
+
+ _handleKeyDown(e) {
+ const code = this._getKeyCode(e);
+ let keysym = KeyboardUtil.getKeysym(e);
+
+ // Windows doesn't have a proper AltGr, but handles it using
+ // fake Ctrl+Alt. However the remote end might not be Windows,
+ // so we need to merge those in to a single AltGr event. We
+ // detect this case by seeing the two key events directly after
+ // each other with a very short time between them (<50ms).
+ if (this._altGrArmed) {
+ this._altGrArmed = false;
+ clearTimeout(this._altGrTimeout);
+
+ if ((code === "AltRight") &&
+ ((e.timeStamp - this._altGrCtrlTime) < 50)) {
+ // FIXME: We fail to detect this if either Ctrl key is
+ // first manually pressed as Windows then no
+ // longer sends the fake Ctrl down event. It
+ // does however happily send real Ctrl events
+ // even when AltGr is already down. Some
+ // browsers detect this for us though and set the
+ // key to "AltGraph".
+ keysym = KeyTable.XK_ISO_Level3_Shift;
+ } else {
+ this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
+ }
+ }
+
+ // We cannot handle keys we cannot track, but we also need
+ // to deal with virtual keyboards which omit key info
+ // (iOS omits tracking info on keyup events, which forces us to
+ // special treat that platform here)
+ if ((code === 'Unidentified') || browser.isIOS()) {
+ if (keysym) {
+ // If it's a virtual keyboard then it should be
+ // sufficient to just send press and release right
+ // after each other
+ this._sendKeyEvent(keysym, code, true);
+ this._sendKeyEvent(keysym, code, false);
+ }
+
+ stopEvent(e);
+ return;
+ }
+
+ // Alt behaves more like AltGraph on macOS, so shuffle the
+ // keys around a bit to make things more sane for the remote
+ // server. This method is used by RealVNC and TigerVNC (and
+ // possibly others).
+ if (browser.isMac()) {
+ switch (keysym) {
+ case KeyTable.XK_Super_L:
+ keysym = KeyTable.XK_Alt_L;
+ break;
+ case KeyTable.XK_Super_R:
+ keysym = KeyTable.XK_Super_L;
+ break;
+ case KeyTable.XK_Alt_L:
+ keysym = KeyTable.XK_Mode_switch;
+ break;
+ case KeyTable.XK_Alt_R:
+ keysym = KeyTable.XK_ISO_Level3_Shift;
+ break;
+ }
+ }
+
+ // Is this key already pressed? If so, then we must use the
+ // same keysym or we'll confuse the server
+ if (code in this._keyDownList) {
+ keysym = this._keyDownList[code];
+ }
+
+ // macOS doesn't send proper key events for modifiers, only
+ // state change events. That gets extra confusing for CapsLock
+ // which toggles on each press, but not on release. So pretend
+ // it was a quick press and release of the button.
+ if (browser.isMac() && (code === 'CapsLock')) {
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
+ stopEvent(e);
+ return;
+ }
+
+ // If this is a legacy browser then we'll need to wait for
+ // a keypress event as well
+ // (IE and Edge has a broken KeyboardEvent.key, so we can't
+ // just check for the presence of that field)
+ if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) {
+ this._pendingKey = code;
+ // However we might not get a keypress event if the key
+ // is non-printable, which needs some special fallback
+ // handling
+ setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
+ return;
+ }
+
+ this._pendingKey = null;
+ stopEvent(e);
+
+ // Possible start of AltGr sequence? (see above)
+ if ((code === "ControlLeft") && browser.isWindows() &&
+ !("ControlLeft" in this._keyDownList)) {
+ this._altGrArmed = true;
+ this._altGrTimeout = setTimeout(this._handleAltGrTimeout.bind(this), 100);
+ this._altGrCtrlTime = e.timeStamp;
+ return;
+ }
+
+ this._sendKeyEvent(keysym, code, true);
+ }
+
+ // Legacy event for browsers without code/key
+ _handleKeyPress(e) {
+ stopEvent(e);
+
+ // Are we expecting a keypress?
+ if (this._pendingKey === null) {
+ return;
+ }
+
+ let code = this._getKeyCode(e);
+ const keysym = KeyboardUtil.getKeysym(e);
+
+ // The key we were waiting for?
+ if ((code !== 'Unidentified') && (code != this._pendingKey)) {
+ return;
+ }
+
+ code = this._pendingKey;
+ this._pendingKey = null;
+
+ if (!keysym) {
+ Log.Info('keypress with no keysym:', e);
+ return;
+ }
+
+ this._sendKeyEvent(keysym, code, true);
+ }
+
+ _handleKeyPressTimeout(e) {
+ // Did someone manage to sort out the key already?
+ if (this._pendingKey === null) {
+ return;
+ }
+
+ let keysym;
+
+ const code = this._pendingKey;
+ this._pendingKey = null;
+
+ // We have no way of knowing the proper keysym with the
+ // information given, but the following are true for most
+ // layouts
+ if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
+ // Digit
+ keysym = e.keyCode;
+ } else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
+ // Character (A-Z)
+ let char = String.fromCharCode(e.keyCode);
+ // A feeble attempt at the correct case
+ if (e.shiftKey) {
+ char = char.toUpperCase();
+ } else {
+ char = char.toLowerCase();
+ }
+ keysym = char.charCodeAt();
+ } else {
+ // Unknown, give up
+ keysym = 0;
+ }
+
+ this._sendKeyEvent(keysym, code, true);
+ }
+
+ _handleKeyUp(e) {
+ stopEvent(e);
+
+ const code = this._getKeyCode(e);
+
+ // We can't get a release in the middle of an AltGr sequence, so
+ // abort that detection
+ if (this._altGrArmed) {
+ this._altGrArmed = false;
+ clearTimeout(this._altGrTimeout);
+ this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
+ }
+
+ // See comment in _handleKeyDown()
+ if (browser.isMac() && (code === 'CapsLock')) {
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
+ return;
+ }
+
+ this._sendKeyEvent(this._keyDownList[code], code, false);
+ }
+
+ _handleAltGrTimeout() {
+ this._altGrArmed = false;
+ clearTimeout(this._altGrTimeout);
+ this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
+ }
+
+ _allKeysUp() {
+ Log.Debug(">> Keyboard.allKeysUp");
+ for (let code in this._keyDownList) {
+ this._sendKeyEvent(this._keyDownList[code], code, false);
+ }
+ Log.Debug("<< Keyboard.allKeysUp");
+ }
+
+ // Firefox Alt workaround, see below
+ _checkAlt(e) {
+ if (e.altKey) {
+ return;
+ }
+
+ const target = this._target;
+ const downList = this._keyDownList;
+ ['AltLeft', 'AltRight'].forEach((code) => {
+ if (!(code in downList)) {
+ return;
+ }
+
+ const event = new KeyboardEvent('keyup',
+ { key: downList[code],
+ code: code });
+ target.dispatchEvent(event);
+ });
+ }
+
+ // ===== PUBLIC METHODS =====
+
+ grab() {
+ //Log.Debug(">> Keyboard.grab");
+
+ this._target.addEventListener('keydown', this._eventHandlers.keydown);
+ this._target.addEventListener('keyup', this._eventHandlers.keyup);
+ this._target.addEventListener('keypress', this._eventHandlers.keypress);
+
+ // Release (key up) if window loses focus
+ window.addEventListener('blur', this._eventHandlers.blur);
+
+ // Firefox has broken handling of Alt, so we need to poll as
+ // best we can for releases (still doesn't prevent the menu
+ // from popping up though as we can't call preventDefault())
+ if (browser.isWindows() && browser.isFirefox()) {
+ const handler = this._eventHandlers.checkalt;
+ ['mousedown', 'mouseup', 'mousemove', 'wheel',
+ 'touchstart', 'touchend', 'touchmove',
+ 'keydown', 'keyup'].forEach(type =>
+ document.addEventListener(type, handler,
+ { capture: true,
+ passive: true }));
+ }
+
+ //Log.Debug("<< Keyboard.grab");
+ }
+
+ ungrab() {
+ //Log.Debug(">> Keyboard.ungrab");
+
+ if (browser.isWindows() && browser.isFirefox()) {
+ const handler = this._eventHandlers.checkalt;
+ ['mousedown', 'mouseup', 'mousemove', 'wheel',
+ 'touchstart', 'touchend', 'touchmove',
+ 'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
+ }
+
+ this._target.removeEventListener('keydown', this._eventHandlers.keydown);
+ this._target.removeEventListener('keyup', this._eventHandlers.keyup);
+ this._target.removeEventListener('keypress', this._eventHandlers.keypress);
+ window.removeEventListener('blur', this._eventHandlers.blur);
+
+ // Release (key up) all keys that are in a down state
+ this._allKeysUp();
+
+ //Log.Debug(">> Keyboard.ungrab");
+ }
+}
diff --git a/systemvm/agent/noVNC/core/input/keysym.js b/systemvm/agent/noVNC/core/input/keysym.js
new file mode 100644
index 0000000..22ba058
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/keysym.js
@@ -0,0 +1,616 @@
+/* eslint-disable key-spacing */
+
+export default {
+ XK_VoidSymbol: 0xffffff, /* Void symbol */
+
+ XK_BackSpace: 0xff08, /* Back space, back char */
+ XK_Tab: 0xff09,
+ XK_Linefeed: 0xff0a, /* Linefeed, LF */
+ XK_Clear: 0xff0b,
+ XK_Return: 0xff0d, /* Return, enter */
+ XK_Pause: 0xff13, /* Pause, hold */
+ XK_Scroll_Lock: 0xff14,
+ XK_Sys_Req: 0xff15,
+ XK_Escape: 0xff1b,
+ XK_Delete: 0xffff, /* Delete, rubout */
+
+ /* International & multi-key character composition */
+
+ XK_Multi_key: 0xff20, /* Multi-key character compose */
+ XK_Codeinput: 0xff37,
+ XK_SingleCandidate: 0xff3c,
+ XK_MultipleCandidate: 0xff3d,
+ XK_PreviousCandidate: 0xff3e,
+
+ /* Japanese keyboard support */
+
+ XK_Kanji: 0xff21, /* Kanji, Kanji convert */
+ XK_Muhenkan: 0xff22, /* Cancel Conversion */
+ XK_Henkan_Mode: 0xff23, /* Start/Stop Conversion */
+ XK_Henkan: 0xff23, /* Alias for Henkan_Mode */
+ XK_Romaji: 0xff24, /* to Romaji */
+ XK_Hiragana: 0xff25, /* to Hiragana */
+ XK_Katakana: 0xff26, /* to Katakana */
+ XK_Hiragana_Katakana: 0xff27, /* Hiragana/Katakana toggle */
+ XK_Zenkaku: 0xff28, /* to Zenkaku */
+ XK_Hankaku: 0xff29, /* to Hankaku */
+ XK_Zenkaku_Hankaku: 0xff2a, /* Zenkaku/Hankaku toggle */
+ XK_Touroku: 0xff2b, /* Add to Dictionary */
+ XK_Massyo: 0xff2c, /* Delete from Dictionary */
+ XK_Kana_Lock: 0xff2d, /* Kana Lock */
+ XK_Kana_Shift: 0xff2e, /* Kana Shift */
+ XK_Eisu_Shift: 0xff2f, /* Alphanumeric Shift */
+ XK_Eisu_toggle: 0xff30, /* Alphanumeric toggle */
+ XK_Kanji_Bangou: 0xff37, /* Codeinput */
+ XK_Zen_Koho: 0xff3d, /* Multiple/All Candidate(s) */
+ XK_Mae_Koho: 0xff3e, /* Previous Candidate */
+
+ /* Cursor control & motion */
+
+ XK_Home: 0xff50,
+ XK_Left: 0xff51, /* Move left, left arrow */
+ XK_Up: 0xff52, /* Move up, up arrow */
+ XK_Right: 0xff53, /* Move right, right arrow */
+ XK_Down: 0xff54, /* Move down, down arrow */
+ XK_Prior: 0xff55, /* Prior, previous */
+ XK_Page_Up: 0xff55,
+ XK_Next: 0xff56, /* Next */
+ XK_Page_Down: 0xff56,
+ XK_End: 0xff57, /* EOL */
+ XK_Begin: 0xff58, /* BOL */
+
+
+ /* Misc functions */
+
+ XK_Select: 0xff60, /* Select, mark */
+ XK_Print: 0xff61,
+ XK_Execute: 0xff62, /* Execute, run, do */
+ XK_Insert: 0xff63, /* Insert, insert here */
+ XK_Undo: 0xff65,
+ XK_Redo: 0xff66, /* Redo, again */
+ XK_Menu: 0xff67,
+ XK_Find: 0xff68, /* Find, search */
+ XK_Cancel: 0xff69, /* Cancel, stop, abort, exit */
+ XK_Help: 0xff6a, /* Help */
+ XK_Break: 0xff6b,
+ XK_Mode_switch: 0xff7e, /* Character set switch */
+ XK_script_switch: 0xff7e, /* Alias for mode_switch */
+ XK_Num_Lock: 0xff7f,
+
+ /* Keypad functions, keypad numbers cleverly chosen to map to ASCII */
+
+ XK_KP_Space: 0xff80, /* Space */
+ XK_KP_Tab: 0xff89,
+ XK_KP_Enter: 0xff8d, /* Enter */
+ XK_KP_F1: 0xff91, /* PF1, KP_A, ... */
+ XK_KP_F2: 0xff92,
+ XK_KP_F3: 0xff93,
+ XK_KP_F4: 0xff94,
+ XK_KP_Home: 0xff95,
+ XK_KP_Left: 0xff96,
+ XK_KP_Up: 0xff97,
+ XK_KP_Right: 0xff98,
+ XK_KP_Down: 0xff99,
+ XK_KP_Prior: 0xff9a,
+ XK_KP_Page_Up: 0xff9a,
+ XK_KP_Next: 0xff9b,
+ XK_KP_Page_Down: 0xff9b,
+ XK_KP_End: 0xff9c,
+ XK_KP_Begin: 0xff9d,
+ XK_KP_Insert: 0xff9e,
+ XK_KP_Delete: 0xff9f,
+ XK_KP_Equal: 0xffbd, /* Equals */
+ XK_KP_Multiply: 0xffaa,
+ XK_KP_Add: 0xffab,
+ XK_KP_Separator: 0xffac, /* Separator, often comma */
+ XK_KP_Subtract: 0xffad,
+ XK_KP_Decimal: 0xffae,
+ XK_KP_Divide: 0xffaf,
+
+ XK_KP_0: 0xffb0,
+ XK_KP_1: 0xffb1,
+ XK_KP_2: 0xffb2,
+ XK_KP_3: 0xffb3,
+ XK_KP_4: 0xffb4,
+ XK_KP_5: 0xffb5,
+ XK_KP_6: 0xffb6,
+ XK_KP_7: 0xffb7,
+ XK_KP_8: 0xffb8,
+ XK_KP_9: 0xffb9,
+
+ /*
+ * Auxiliary functions; note the duplicate definitions for left and right
+ * function keys; Sun keyboards and a few other manufacturers have such
+ * function key groups on the left and/or right sides of the keyboard.
+ * We've not found a keyboard with more than 35 function keys total.
+ */
+
+ XK_F1: 0xffbe,
+ XK_F2: 0xffbf,
+ XK_F3: 0xffc0,
+ XK_F4: 0xffc1,
+ XK_F5: 0xffc2,
+ XK_F6: 0xffc3,
+ XK_F7: 0xffc4,
+ XK_F8: 0xffc5,
+ XK_F9: 0xffc6,
+ XK_F10: 0xffc7,
+ XK_F11: 0xffc8,
+ XK_L1: 0xffc8,
+ XK_F12: 0xffc9,
+ XK_L2: 0xffc9,
+ XK_F13: 0xffca,
+ XK_L3: 0xffca,
+ XK_F14: 0xffcb,
+ XK_L4: 0xffcb,
+ XK_F15: 0xffcc,
+ XK_L5: 0xffcc,
+ XK_F16: 0xffcd,
+ XK_L6: 0xffcd,
+ XK_F17: 0xffce,
+ XK_L7: 0xffce,
+ XK_F18: 0xffcf,
+ XK_L8: 0xffcf,
+ XK_F19: 0xffd0,
+ XK_L9: 0xffd0,
+ XK_F20: 0xffd1,
+ XK_L10: 0xffd1,
+ XK_F21: 0xffd2,
+ XK_R1: 0xffd2,
+ XK_F22: 0xffd3,
+ XK_R2: 0xffd3,
+ XK_F23: 0xffd4,
+ XK_R3: 0xffd4,
+ XK_F24: 0xffd5,
+ XK_R4: 0xffd5,
+ XK_F25: 0xffd6,
+ XK_R5: 0xffd6,
+ XK_F26: 0xffd7,
+ XK_R6: 0xffd7,
+ XK_F27: 0xffd8,
+ XK_R7: 0xffd8,
+ XK_F28: 0xffd9,
+ XK_R8: 0xffd9,
+ XK_F29: 0xffda,
+ XK_R9: 0xffda,
+ XK_F30: 0xffdb,
+ XK_R10: 0xffdb,
+ XK_F31: 0xffdc,
+ XK_R11: 0xffdc,
+ XK_F32: 0xffdd,
+ XK_R12: 0xffdd,
+ XK_F33: 0xffde,
+ XK_R13: 0xffde,
+ XK_F34: 0xffdf,
+ XK_R14: 0xffdf,
+ XK_F35: 0xffe0,
+ XK_R15: 0xffe0,
+
+ /* Modifiers */
+
+ XK_Shift_L: 0xffe1, /* Left shift */
+ XK_Shift_R: 0xffe2, /* Right shift */
+ XK_Control_L: 0xffe3, /* Left control */
+ XK_Control_R: 0xffe4, /* Right control */
+ XK_Caps_Lock: 0xffe5, /* Caps lock */
+ XK_Shift_Lock: 0xffe6, /* Shift lock */
+
+ XK_Meta_L: 0xffe7, /* Left meta */
+ XK_Meta_R: 0xffe8, /* Right meta */
+ XK_Alt_L: 0xffe9, /* Left alt */
+ XK_Alt_R: 0xffea, /* Right alt */
+ XK_Super_L: 0xffeb, /* Left super */
+ XK_Super_R: 0xffec, /* Right super */
+ XK_Hyper_L: 0xffed, /* Left hyper */
+ XK_Hyper_R: 0xffee, /* Right hyper */
+
+ /*
+ * Keyboard (XKB) Extension function and modifier keys
+ * (from Appendix C of "The X Keyboard Extension: Protocol Specification")
+ * Byte 3 = 0xfe
+ */
+
+ XK_ISO_Level3_Shift: 0xfe03, /* AltGr */
+ XK_ISO_Next_Group: 0xfe08,
+ XK_ISO_Prev_Group: 0xfe0a,
+ XK_ISO_First_Group: 0xfe0c,
+ XK_ISO_Last_Group: 0xfe0e,
+
+ /*
+ * Latin 1
+ * (ISO/IEC 8859-1: Unicode U+0020..U+00FF)
+ * Byte 3: 0
+ */
+
+ XK_space: 0x0020, /* U+0020 SPACE */
+ XK_exclam: 0x0021, /* U+0021 EXCLAMATION MARK */
+ XK_quotedbl: 0x0022, /* U+0022 QUOTATION MARK */
+ XK_numbersign: 0x0023, /* U+0023 NUMBER SIGN */
+ XK_dollar: 0x0024, /* U+0024 DOLLAR SIGN */
+ XK_percent: 0x0025, /* U+0025 PERCENT SIGN */
+ XK_ampersand: 0x0026, /* U+0026 AMPERSAND */
+ XK_apostrophe: 0x0027, /* U+0027 APOSTROPHE */
+ XK_quoteright: 0x0027, /* deprecated */
+ XK_parenleft: 0x0028, /* U+0028 LEFT PARENTHESIS */
+ XK_parenright: 0x0029, /* U+0029 RIGHT PARENTHESIS */
+ XK_asterisk: 0x002a, /* U+002A ASTERISK */
+ XK_plus: 0x002b, /* U+002B PLUS SIGN */
+ XK_comma: 0x002c, /* U+002C COMMA */
+ XK_minus: 0x002d, /* U+002D HYPHEN-MINUS */
+ XK_period: 0x002e, /* U+002E FULL STOP */
+ XK_slash: 0x002f, /* U+002F SOLIDUS */
+ XK_0: 0x0030, /* U+0030 DIGIT ZERO */
+ XK_1: 0x0031, /* U+0031 DIGIT ONE */
+ XK_2: 0x0032, /* U+0032 DIGIT TWO */
+ XK_3: 0x0033, /* U+0033 DIGIT THREE */
+ XK_4: 0x0034, /* U+0034 DIGIT FOUR */
+ XK_5: 0x0035, /* U+0035 DIGIT FIVE */
+ XK_6: 0x0036, /* U+0036 DIGIT SIX */
+ XK_7: 0x0037, /* U+0037 DIGIT SEVEN */
+ XK_8: 0x0038, /* U+0038 DIGIT EIGHT */
+ XK_9: 0x0039, /* U+0039 DIGIT NINE */
+ XK_colon: 0x003a, /* U+003A COLON */
+ XK_semicolon: 0x003b, /* U+003B SEMICOLON */
+ XK_less: 0x003c, /* U+003C LESS-THAN SIGN */
+ XK_equal: 0x003d, /* U+003D EQUALS SIGN */
+ XK_greater: 0x003e, /* U+003E GREATER-THAN SIGN */
+ XK_question: 0x003f, /* U+003F QUESTION MARK */
+ XK_at: 0x0040, /* U+0040 COMMERCIAL AT */
+ XK_A: 0x0041, /* U+0041 LATIN CAPITAL LETTER A */
+ XK_B: 0x0042, /* U+0042 LATIN CAPITAL LETTER B */
+ XK_C: 0x0043, /* U+0043 LATIN CAPITAL LETTER C */
+ XK_D: 0x0044, /* U+0044 LATIN CAPITAL LETTER D */
+ XK_E: 0x0045, /* U+0045 LATIN CAPITAL LETTER E */
+ XK_F: 0x0046, /* U+0046 LATIN CAPITAL LETTER F */
+ XK_G: 0x0047, /* U+0047 LATIN CAPITAL LETTER G */
+ XK_H: 0x0048, /* U+0048 LATIN CAPITAL LETTER H */
+ XK_I: 0x0049, /* U+0049 LATIN CAPITAL LETTER I */
+ XK_J: 0x004a, /* U+004A LATIN CAPITAL LETTER J */
+ XK_K: 0x004b, /* U+004B LATIN CAPITAL LETTER K */
+ XK_L: 0x004c, /* U+004C LATIN CAPITAL LETTER L */
+ XK_M: 0x004d, /* U+004D LATIN CAPITAL LETTER M */
+ XK_N: 0x004e, /* U+004E LATIN CAPITAL LETTER N */
+ XK_O: 0x004f, /* U+004F LATIN CAPITAL LETTER O */
+ XK_P: 0x0050, /* U+0050 LATIN CAPITAL LETTER P */
+ XK_Q: 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */
+ XK_R: 0x0052, /* U+0052 LATIN CAPITAL LETTER R */
+ XK_S: 0x0053, /* U+0053 LATIN CAPITAL LETTER S */
+ XK_T: 0x0054, /* U+0054 LATIN CAPITAL LETTER T */
+ XK_U: 0x0055, /* U+0055 LATIN CAPITAL LETTER U */
+ XK_V: 0x0056, /* U+0056 LATIN CAPITAL LETTER V */
+ XK_W: 0x0057, /* U+0057 LATIN CAPITAL LETTER W */
+ XK_X: 0x0058, /* U+0058 LATIN CAPITAL LETTER X */
+ XK_Y: 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */
+ XK_Z: 0x005a, /* U+005A LATIN CAPITAL LETTER Z */
+ XK_bracketleft: 0x005b, /* U+005B LEFT SQUARE BRACKET */
+ XK_backslash: 0x005c, /* U+005C REVERSE SOLIDUS */
+ XK_bracketright: 0x005d, /* U+005D RIGHT SQUARE BRACKET */
+ XK_asciicircum: 0x005e, /* U+005E CIRCUMFLEX ACCENT */
+ XK_underscore: 0x005f, /* U+005F LOW LINE */
+ XK_grave: 0x0060, /* U+0060 GRAVE ACCENT */
+ XK_quoteleft: 0x0060, /* deprecated */
+ XK_a: 0x0061, /* U+0061 LATIN SMALL LETTER A */
+ XK_b: 0x0062, /* U+0062 LATIN SMALL LETTER B */
+ XK_c: 0x0063, /* U+0063 LATIN SMALL LETTER C */
+ XK_d: 0x0064, /* U+0064 LATIN SMALL LETTER D */
+ XK_e: 0x0065, /* U+0065 LATIN SMALL LETTER E */
+ XK_f: 0x0066, /* U+0066 LATIN SMALL LETTER F */
+ XK_g: 0x0067, /* U+0067 LATIN SMALL LETTER G */
+ XK_h: 0x0068, /* U+0068 LATIN SMALL LETTER H */
+ XK_i: 0x0069, /* U+0069 LATIN SMALL LETTER I */
+ XK_j: 0x006a, /* U+006A LATIN SMALL LETTER J */
+ XK_k: 0x006b, /* U+006B LATIN SMALL LETTER K */
+ XK_l: 0x006c, /* U+006C LATIN SMALL LETTER L */
+ XK_m: 0x006d, /* U+006D LATIN SMALL LETTER M */
+ XK_n: 0x006e, /* U+006E LATIN SMALL LETTER N */
+ XK_o: 0x006f, /* U+006F LATIN SMALL LETTER O */
+ XK_p: 0x0070, /* U+0070 LATIN SMALL LETTER P */
+ XK_q: 0x0071, /* U+0071 LATIN SMALL LETTER Q */
+ XK_r: 0x0072, /* U+0072 LATIN SMALL LETTER R */
+ XK_s: 0x0073, /* U+0073 LATIN SMALL LETTER S */
+ XK_t: 0x0074, /* U+0074 LATIN SMALL LETTER T */
+ XK_u: 0x0075, /* U+0075 LATIN SMALL LETTER U */
+ XK_v: 0x0076, /* U+0076 LATIN SMALL LETTER V */
+ XK_w: 0x0077, /* U+0077 LATIN SMALL LETTER W */
+ XK_x: 0x0078, /* U+0078 LATIN SMALL LETTER X */
+ XK_y: 0x0079, /* U+0079 LATIN SMALL LETTER Y */
+ XK_z: 0x007a, /* U+007A LATIN SMALL LETTER Z */
+ XK_braceleft: 0x007b, /* U+007B LEFT CURLY BRACKET */
+ XK_bar: 0x007c, /* U+007C VERTICAL LINE */
+ XK_braceright: 0x007d, /* U+007D RIGHT CURLY BRACKET */
+ XK_asciitilde: 0x007e, /* U+007E TILDE */
+
+ XK_nobreakspace: 0x00a0, /* U+00A0 NO-BREAK SPACE */
+ XK_exclamdown: 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */
+ XK_cent: 0x00a2, /* U+00A2 CENT SIGN */
+ XK_sterling: 0x00a3, /* U+00A3 POUND SIGN */
+ XK_currency: 0x00a4, /* U+00A4 CURRENCY SIGN */
+ XK_yen: 0x00a5, /* U+00A5 YEN SIGN */
+ XK_brokenbar: 0x00a6, /* U+00A6 BROKEN BAR */
+ XK_section: 0x00a7, /* U+00A7 SECTION SIGN */
+ XK_diaeresis: 0x00a8, /* U+00A8 DIAERESIS */
+ XK_copyright: 0x00a9, /* U+00A9 COPYRIGHT SIGN */
+ XK_ordfeminine: 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */
+ XK_guillemotleft: 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ XK_notsign: 0x00ac, /* U+00AC NOT SIGN */
+ XK_hyphen: 0x00ad, /* U+00AD SOFT HYPHEN */
+ XK_registered: 0x00ae, /* U+00AE REGISTERED SIGN */
+ XK_macron: 0x00af, /* U+00AF MACRON */
+ XK_degree: 0x00b0, /* U+00B0 DEGREE SIGN */
+ XK_plusminus: 0x00b1, /* U+00B1 PLUS-MINUS SIGN */
+ XK_twosuperior: 0x00b2, /* U+00B2 SUPERSCRIPT TWO */
+ XK_threesuperior: 0x00b3, /* U+00B3 SUPERSCRIPT THREE */
+ XK_acute: 0x00b4, /* U+00B4 ACUTE ACCENT */
+ XK_mu: 0x00b5, /* U+00B5 MICRO SIGN */
+ XK_paragraph: 0x00b6, /* U+00B6 PILCROW SIGN */
+ XK_periodcentered: 0x00b7, /* U+00B7 MIDDLE DOT */
+ XK_cedilla: 0x00b8, /* U+00B8 CEDILLA */
+ XK_onesuperior: 0x00b9, /* U+00B9 SUPERSCRIPT ONE */
+ XK_masculine: 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */
+ XK_guillemotright: 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ XK_onequarter: 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */
+ XK_onehalf: 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */
+ XK_threequarters: 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */
+ XK_questiondown: 0x00bf, /* U+00BF INVERTED QUESTION MARK */
+ XK_Agrave: 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */
+ XK_Aacute: 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */
+ XK_Acircumflex: 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+ XK_Atilde: 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */
+ XK_Adiaeresis: 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */
+ XK_Aring: 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */
+ XK_AE: 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */
+ XK_Ccedilla: 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */
+ XK_Egrave: 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */
+ XK_Eacute: 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
+ XK_Ecircumflex: 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+ XK_Ediaeresis: 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */
+ XK_Igrave: 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */
+ XK_Iacute: 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */
+ XK_Icircumflex: 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+ XK_Idiaeresis: 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */
+ XK_ETH: 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */
+ XK_Eth: 0x00d0, /* deprecated */
+ XK_Ntilde: 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */
+ XK_Ograve: 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */
+ XK_Oacute: 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */
+ XK_Ocircumflex: 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+ XK_Otilde: 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */
+ XK_Odiaeresis: 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */
+ XK_multiply: 0x00d7, /* U+00D7 MULTIPLICATION SIGN */
+ XK_Oslash: 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
+ XK_Ooblique: 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
+ XK_Ugrave: 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */
+ XK_Uacute: 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */
+ XK_Ucircumflex: 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+ XK_Udiaeresis: 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */
+ XK_Yacute: 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */
+ XK_THORN: 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */
+ XK_Thorn: 0x00de, /* deprecated */
+ XK_ssharp: 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */
+ XK_agrave: 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */
+ XK_aacute: 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */
+ XK_acircumflex: 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */
+ XK_atilde: 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */
+ XK_adiaeresis: 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */
+ XK_aring: 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */
+ XK_ae: 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */
+ XK_ccedilla: 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */
+ XK_egrave: 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */
+ XK_eacute: 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
+ XK_ecircumflex: 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */
+ XK_ediaeresis: 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */
+ XK_igrave: 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */
+ XK_iacute: 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */
+ XK_icircumflex: 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */
+ XK_idiaeresis: 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */
+ XK_eth: 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */
+ XK_ntilde: 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */
+ XK_ograve: 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */
+ XK_oacute: 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */
+ XK_ocircumflex: 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */
+ XK_otilde: 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */
+ XK_odiaeresis: 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */
+ XK_division: 0x00f7, /* U+00F7 DIVISION SIGN */
+ XK_oslash: 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
+ XK_ooblique: 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
+ XK_ugrave: 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */
+ XK_uacute: 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */
+ XK_ucircumflex: 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */
+ XK_udiaeresis: 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */
+ XK_yacute: 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
+ XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
+ XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
+
+ /*
+ * Korean
+ * Byte 3 = 0x0e
+ */
+
+ XK_Hangul: 0xff31, /* Hangul start/stop(toggle) */
+ XK_Hangul_Hanja: 0xff34, /* Start Hangul->Hanja Conversion */
+ XK_Hangul_Jeonja: 0xff38, /* Jeonja mode */
+
+ /*
+ * XFree86 vendor specific keysyms.
+ *
+ * The XFree86 keysym range is 0x10080001 - 0x1008FFFF.
+ */
+
+ XF86XK_ModeLock: 0x1008FF01,
+ XF86XK_MonBrightnessUp: 0x1008FF02,
+ XF86XK_MonBrightnessDown: 0x1008FF03,
+ XF86XK_KbdLightOnOff: 0x1008FF04,
+ XF86XK_KbdBrightnessUp: 0x1008FF05,
+ XF86XK_KbdBrightnessDown: 0x1008FF06,
+ XF86XK_Standby: 0x1008FF10,
+ XF86XK_AudioLowerVolume: 0x1008FF11,
+ XF86XK_AudioMute: 0x1008FF12,
+ XF86XK_AudioRaiseVolume: 0x1008FF13,
+ XF86XK_AudioPlay: 0x1008FF14,
+ XF86XK_AudioStop: 0x1008FF15,
+ XF86XK_AudioPrev: 0x1008FF16,
+ XF86XK_AudioNext: 0x1008FF17,
+ XF86XK_HomePage: 0x1008FF18,
+ XF86XK_Mail: 0x1008FF19,
+ XF86XK_Start: 0x1008FF1A,
+ XF86XK_Search: 0x1008FF1B,
+ XF86XK_AudioRecord: 0x1008FF1C,
+ XF86XK_Calculator: 0x1008FF1D,
+ XF86XK_Memo: 0x1008FF1E,
+ XF86XK_ToDoList: 0x1008FF1F,
+ XF86XK_Calendar: 0x1008FF20,
+ XF86XK_PowerDown: 0x1008FF21,
+ XF86XK_ContrastAdjust: 0x1008FF22,
+ XF86XK_RockerUp: 0x1008FF23,
+ XF86XK_RockerDown: 0x1008FF24,
+ XF86XK_RockerEnter: 0x1008FF25,
+ XF86XK_Back: 0x1008FF26,
+ XF86XK_Forward: 0x1008FF27,
+ XF86XK_Stop: 0x1008FF28,
+ XF86XK_Refresh: 0x1008FF29,
+ XF86XK_PowerOff: 0x1008FF2A,
+ XF86XK_WakeUp: 0x1008FF2B,
+ XF86XK_Eject: 0x1008FF2C,
+ XF86XK_ScreenSaver: 0x1008FF2D,
+ XF86XK_WWW: 0x1008FF2E,
+ XF86XK_Sleep: 0x1008FF2F,
+ XF86XK_Favorites: 0x1008FF30,
+ XF86XK_AudioPause: 0x1008FF31,
+ XF86XK_AudioMedia: 0x1008FF32,
+ XF86XK_MyComputer: 0x1008FF33,
+ XF86XK_VendorHome: 0x1008FF34,
+ XF86XK_LightBulb: 0x1008FF35,
+ XF86XK_Shop: 0x1008FF36,
+ XF86XK_History: 0x1008FF37,
+ XF86XK_OpenURL: 0x1008FF38,
+ XF86XK_AddFavorite: 0x1008FF39,
+ XF86XK_HotLinks: 0x1008FF3A,
+ XF86XK_BrightnessAdjust: 0x1008FF3B,
+ XF86XK_Finance: 0x1008FF3C,
+ XF86XK_Community: 0x1008FF3D,
+ XF86XK_AudioRewind: 0x1008FF3E,
+ XF86XK_BackForward: 0x1008FF3F,
+ XF86XK_Launch0: 0x1008FF40,
+ XF86XK_Launch1: 0x1008FF41,
+ XF86XK_Launch2: 0x1008FF42,
+ XF86XK_Launch3: 0x1008FF43,
+ XF86XK_Launch4: 0x1008FF44,
+ XF86XK_Launch5: 0x1008FF45,
+ XF86XK_Launch6: 0x1008FF46,
+ XF86XK_Launch7: 0x1008FF47,
+ XF86XK_Launch8: 0x1008FF48,
+ XF86XK_Launch9: 0x1008FF49,
+ XF86XK_LaunchA: 0x1008FF4A,
+ XF86XK_LaunchB: 0x1008FF4B,
+ XF86XK_LaunchC: 0x1008FF4C,
+ XF86XK_LaunchD: 0x1008FF4D,
+ XF86XK_LaunchE: 0x1008FF4E,
+ XF86XK_LaunchF: 0x1008FF4F,
+ XF86XK_ApplicationLeft: 0x1008FF50,
+ XF86XK_ApplicationRight: 0x1008FF51,
+ XF86XK_Book: 0x1008FF52,
+ XF86XK_CD: 0x1008FF53,
+ XF86XK_Calculater: 0x1008FF54,
+ XF86XK_Clear: 0x1008FF55,
+ XF86XK_Close: 0x1008FF56,
+ XF86XK_Copy: 0x1008FF57,
+ XF86XK_Cut: 0x1008FF58,
+ XF86XK_Display: 0x1008FF59,
+ XF86XK_DOS: 0x1008FF5A,
+ XF86XK_Documents: 0x1008FF5B,
+ XF86XK_Excel: 0x1008FF5C,
+ XF86XK_Explorer: 0x1008FF5D,
+ XF86XK_Game: 0x1008FF5E,
+ XF86XK_Go: 0x1008FF5F,
+ XF86XK_iTouch: 0x1008FF60,
+ XF86XK_LogOff: 0x1008FF61,
+ XF86XK_Market: 0x1008FF62,
+ XF86XK_Meeting: 0x1008FF63,
+ XF86XK_MenuKB: 0x1008FF65,
+ XF86XK_MenuPB: 0x1008FF66,
+ XF86XK_MySites: 0x1008FF67,
+ XF86XK_New: 0x1008FF68,
+ XF86XK_News: 0x1008FF69,
+ XF86XK_OfficeHome: 0x1008FF6A,
+ XF86XK_Open: 0x1008FF6B,
+ XF86XK_Option: 0x1008FF6C,
+ XF86XK_Paste: 0x1008FF6D,
+ XF86XK_Phone: 0x1008FF6E,
+ XF86XK_Q: 0x1008FF70,
+ XF86XK_Reply: 0x1008FF72,
+ XF86XK_Reload: 0x1008FF73,
+ XF86XK_RotateWindows: 0x1008FF74,
+ XF86XK_RotationPB: 0x1008FF75,
+ XF86XK_RotationKB: 0x1008FF76,
+ XF86XK_Save: 0x1008FF77,
+ XF86XK_ScrollUp: 0x1008FF78,
+ XF86XK_ScrollDown: 0x1008FF79,
+ XF86XK_ScrollClick: 0x1008FF7A,
+ XF86XK_Send: 0x1008FF7B,
+ XF86XK_Spell: 0x1008FF7C,
+ XF86XK_SplitScreen: 0x1008FF7D,
+ XF86XK_Support: 0x1008FF7E,
+ XF86XK_TaskPane: 0x1008FF7F,
+ XF86XK_Terminal: 0x1008FF80,
+ XF86XK_Tools: 0x1008FF81,
+ XF86XK_Travel: 0x1008FF82,
+ XF86XK_UserPB: 0x1008FF84,
+ XF86XK_User1KB: 0x1008FF85,
+ XF86XK_User2KB: 0x1008FF86,
+ XF86XK_Video: 0x1008FF87,
+ XF86XK_WheelButton: 0x1008FF88,
+ XF86XK_Word: 0x1008FF89,
+ XF86XK_Xfer: 0x1008FF8A,
+ XF86XK_ZoomIn: 0x1008FF8B,
+ XF86XK_ZoomOut: 0x1008FF8C,
+ XF86XK_Away: 0x1008FF8D,
+ XF86XK_Messenger: 0x1008FF8E,
+ XF86XK_WebCam: 0x1008FF8F,
+ XF86XK_MailForward: 0x1008FF90,
+ XF86XK_Pictures: 0x1008FF91,
+ XF86XK_Music: 0x1008FF92,
+ XF86XK_Battery: 0x1008FF93,
+ XF86XK_Bluetooth: 0x1008FF94,
+ XF86XK_WLAN: 0x1008FF95,
+ XF86XK_UWB: 0x1008FF96,
+ XF86XK_AudioForward: 0x1008FF97,
+ XF86XK_AudioRepeat: 0x1008FF98,
+ XF86XK_AudioRandomPlay: 0x1008FF99,
+ XF86XK_Subtitle: 0x1008FF9A,
+ XF86XK_AudioCycleTrack: 0x1008FF9B,
+ XF86XK_CycleAngle: 0x1008FF9C,
+ XF86XK_FrameBack: 0x1008FF9D,
+ XF86XK_FrameForward: 0x1008FF9E,
+ XF86XK_Time: 0x1008FF9F,
+ XF86XK_Select: 0x1008FFA0,
+ XF86XK_View: 0x1008FFA1,
+ XF86XK_TopMenu: 0x1008FFA2,
+ XF86XK_Red: 0x1008FFA3,
+ XF86XK_Green: 0x1008FFA4,
+ XF86XK_Yellow: 0x1008FFA5,
+ XF86XK_Blue: 0x1008FFA6,
+ XF86XK_Suspend: 0x1008FFA7,
+ XF86XK_Hibernate: 0x1008FFA8,
+ XF86XK_TouchpadToggle: 0x1008FFA9,
+ XF86XK_TouchpadOn: 0x1008FFB0,
+ XF86XK_TouchpadOff: 0x1008FFB1,
+ XF86XK_AudioMicMute: 0x1008FFB2,
+ XF86XK_Switch_VT_1: 0x1008FE01,
+ XF86XK_Switch_VT_2: 0x1008FE02,
+ XF86XK_Switch_VT_3: 0x1008FE03,
+ XF86XK_Switch_VT_4: 0x1008FE04,
+ XF86XK_Switch_VT_5: 0x1008FE05,
+ XF86XK_Switch_VT_6: 0x1008FE06,
+ XF86XK_Switch_VT_7: 0x1008FE07,
+ XF86XK_Switch_VT_8: 0x1008FE08,
+ XF86XK_Switch_VT_9: 0x1008FE09,
+ XF86XK_Switch_VT_10: 0x1008FE0A,
+ XF86XK_Switch_VT_11: 0x1008FE0B,
+ XF86XK_Switch_VT_12: 0x1008FE0C,
+ XF86XK_Ungrab: 0x1008FE20,
+ XF86XK_ClearGrab: 0x1008FE21,
+ XF86XK_Next_VMode: 0x1008FE22,
+ XF86XK_Prev_VMode: 0x1008FE23,
+ XF86XK_LogWindowTree: 0x1008FE24,
+ XF86XK_LogGrabInfo: 0x1008FE25,
+};
diff --git a/systemvm/agent/noVNC/core/input/keysymdef.js b/systemvm/agent/noVNC/core/input/keysymdef.js
new file mode 100644
index 0000000..951caca
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/keysymdef.js
@@ -0,0 +1,688 @@
+/*
+ * Mapping from Unicode codepoints to X11/RFB keysyms
+ *
+ * This file was automatically generated from keysymdef.h
+ * DO NOT EDIT!
+ */
+
+/* Functions at the bottom */
+
+const codepoints = {
+ 0x0100: 0x03c0, // XK_Amacron
+ 0x0101: 0x03e0, // XK_amacron
+ 0x0102: 0x01c3, // XK_Abreve
+ 0x0103: 0x01e3, // XK_abreve
+ 0x0104: 0x01a1, // XK_Aogonek
+ 0x0105: 0x01b1, // XK_aogonek
+ 0x0106: 0x01c6, // XK_Cacute
+ 0x0107: 0x01e6, // XK_cacute
+ 0x0108: 0x02c6, // XK_Ccircumflex
+ 0x0109: 0x02e6, // XK_ccircumflex
+ 0x010a: 0x02c5, // XK_Cabovedot
+ 0x010b: 0x02e5, // XK_cabovedot
+ 0x010c: 0x01c8, // XK_Ccaron
+ 0x010d: 0x01e8, // XK_ccaron
+ 0x010e: 0x01cf, // XK_Dcaron
+ 0x010f: 0x01ef, // XK_dcaron
+ 0x0110: 0x01d0, // XK_Dstroke
+ 0x0111: 0x01f0, // XK_dstroke
+ 0x0112: 0x03aa, // XK_Emacron
+ 0x0113: 0x03ba, // XK_emacron
+ 0x0116: 0x03cc, // XK_Eabovedot
+ 0x0117: 0x03ec, // XK_eabovedot
+ 0x0118: 0x01ca, // XK_Eogonek
+ 0x0119: 0x01ea, // XK_eogonek
+ 0x011a: 0x01cc, // XK_Ecaron
+ 0x011b: 0x01ec, // XK_ecaron
+ 0x011c: 0x02d8, // XK_Gcircumflex
+ 0x011d: 0x02f8, // XK_gcircumflex
+ 0x011e: 0x02ab, // XK_Gbreve
+ 0x011f: 0x02bb, // XK_gbreve
+ 0x0120: 0x02d5, // XK_Gabovedot
+ 0x0121: 0x02f5, // XK_gabovedot
+ 0x0122: 0x03ab, // XK_Gcedilla
+ 0x0123: 0x03bb, // XK_gcedilla
+ 0x0124: 0x02a6, // XK_Hcircumflex
+ 0x0125: 0x02b6, // XK_hcircumflex
+ 0x0126: 0x02a1, // XK_Hstroke
+ 0x0127: 0x02b1, // XK_hstroke
+ 0x0128: 0x03a5, // XK_Itilde
+ 0x0129: 0x03b5, // XK_itilde
+ 0x012a: 0x03cf, // XK_Imacron
+ 0x012b: 0x03ef, // XK_imacron
+ 0x012e: 0x03c7, // XK_Iogonek
+ 0x012f: 0x03e7, // XK_iogonek
+ 0x0130: 0x02a9, // XK_Iabovedot
+ 0x0131: 0x02b9, // XK_idotless
+ 0x0134: 0x02ac, // XK_Jcircumflex
+ 0x0135: 0x02bc, // XK_jcircumflex
+ 0x0136: 0x03d3, // XK_Kcedilla
+ 0x0137: 0x03f3, // XK_kcedilla
+ 0x0138: 0x03a2, // XK_kra
+ 0x0139: 0x01c5, // XK_Lacute
+ 0x013a: 0x01e5, // XK_lacute
+ 0x013b: 0x03a6, // XK_Lcedilla
+ 0x013c: 0x03b6, // XK_lcedilla
+ 0x013d: 0x01a5, // XK_Lcaron
+ 0x013e: 0x01b5, // XK_lcaron
+ 0x0141: 0x01a3, // XK_Lstroke
+ 0x0142: 0x01b3, // XK_lstroke
+ 0x0143: 0x01d1, // XK_Nacute
+ 0x0144: 0x01f1, // XK_nacute
+ 0x0145: 0x03d1, // XK_Ncedilla
+ 0x0146: 0x03f1, // XK_ncedilla
+ 0x0147: 0x01d2, // XK_Ncaron
+ 0x0148: 0x01f2, // XK_ncaron
+ 0x014a: 0x03bd, // XK_ENG
+ 0x014b: 0x03bf, // XK_eng
+ 0x014c: 0x03d2, // XK_Omacron
+ 0x014d: 0x03f2, // XK_omacron
+ 0x0150: 0x01d5, // XK_Odoubleacute
+ 0x0151: 0x01f5, // XK_odoubleacute
+ 0x0152: 0x13bc, // XK_OE
+ 0x0153: 0x13bd, // XK_oe
+ 0x0154: 0x01c0, // XK_Racute
+ 0x0155: 0x01e0, // XK_racute
+ 0x0156: 0x03a3, // XK_Rcedilla
+ 0x0157: 0x03b3, // XK_rcedilla
+ 0x0158: 0x01d8, // XK_Rcaron
+ 0x0159: 0x01f8, // XK_rcaron
+ 0x015a: 0x01a6, // XK_Sacute
+ 0x015b: 0x01b6, // XK_sacute
+ 0x015c: 0x02de, // XK_Scircumflex
+ 0x015d: 0x02fe, // XK_scircumflex
+ 0x015e: 0x01aa, // XK_Scedilla
+ 0x015f: 0x01ba, // XK_scedilla
+ 0x0160: 0x01a9, // XK_Scaron
+ 0x0161: 0x01b9, // XK_scaron
+ 0x0162: 0x01de, // XK_Tcedilla
+ 0x0163: 0x01fe, // XK_tcedilla
+ 0x0164: 0x01ab, // XK_Tcaron
+ 0x0165: 0x01bb, // XK_tcaron
+ 0x0166: 0x03ac, // XK_Tslash
+ 0x0167: 0x03bc, // XK_tslash
+ 0x0168: 0x03dd, // XK_Utilde
+ 0x0169: 0x03fd, // XK_utilde
+ 0x016a: 0x03de, // XK_Umacron
+ 0x016b: 0x03fe, // XK_umacron
+ 0x016c: 0x02dd, // XK_Ubreve
+ 0x016d: 0x02fd, // XK_ubreve
+ 0x016e: 0x01d9, // XK_Uring
+ 0x016f: 0x01f9, // XK_uring
+ 0x0170: 0x01db, // XK_Udoubleacute
+ 0x0171: 0x01fb, // XK_udoubleacute
+ 0x0172: 0x03d9, // XK_Uogonek
+ 0x0173: 0x03f9, // XK_uogonek
+ 0x0178: 0x13be, // XK_Ydiaeresis
+ 0x0179: 0x01ac, // XK_Zacute
+ 0x017a: 0x01bc, // XK_zacute
+ 0x017b: 0x01af, // XK_Zabovedot
+ 0x017c: 0x01bf, // XK_zabovedot
+ 0x017d: 0x01ae, // XK_Zcaron
+ 0x017e: 0x01be, // XK_zcaron
+ 0x0192: 0x08f6, // XK_function
+ 0x01d2: 0x10001d1, // XK_Ocaron
+ 0x02c7: 0x01b7, // XK_caron
+ 0x02d8: 0x01a2, // XK_breve
+ 0x02d9: 0x01ff, // XK_abovedot
+ 0x02db: 0x01b2, // XK_ogonek
+ 0x02dd: 0x01bd, // XK_doubleacute
+ 0x0385: 0x07ae, // XK_Greek_accentdieresis
+ 0x0386: 0x07a1, // XK_Greek_ALPHAaccent
+ 0x0388: 0x07a2, // XK_Greek_EPSILONaccent
+ 0x0389: 0x07a3, // XK_Greek_ETAaccent
+ 0x038a: 0x07a4, // XK_Greek_IOTAaccent
+ 0x038c: 0x07a7, // XK_Greek_OMICRONaccent
+ 0x038e: 0x07a8, // XK_Greek_UPSILONaccent
+ 0x038f: 0x07ab, // XK_Greek_OMEGAaccent
+ 0x0390: 0x07b6, // XK_Greek_iotaaccentdieresis
+ 0x0391: 0x07c1, // XK_Greek_ALPHA
+ 0x0392: 0x07c2, // XK_Greek_BETA
+ 0x0393: 0x07c3, // XK_Greek_GAMMA
+ 0x0394: 0x07c4, // XK_Greek_DELTA
+ 0x0395: 0x07c5, // XK_Greek_EPSILON
+ 0x0396: 0x07c6, // XK_Greek_ZETA
+ 0x0397: 0x07c7, // XK_Greek_ETA
+ 0x0398: 0x07c8, // XK_Greek_THETA
+ 0x0399: 0x07c9, // XK_Greek_IOTA
+ 0x039a: 0x07ca, // XK_Greek_KAPPA
+ 0x039b: 0x07cb, // XK_Greek_LAMDA
+ 0x039c: 0x07cc, // XK_Greek_MU
+ 0x039d: 0x07cd, // XK_Greek_NU
+ 0x039e: 0x07ce, // XK_Greek_XI
+ 0x039f: 0x07cf, // XK_Greek_OMICRON
+ 0x03a0: 0x07d0, // XK_Greek_PI
+ 0x03a1: 0x07d1, // XK_Greek_RHO
+ 0x03a3: 0x07d2, // XK_Greek_SIGMA
+ 0x03a4: 0x07d4, // XK_Greek_TAU
+ 0x03a5: 0x07d5, // XK_Greek_UPSILON
+ 0x03a6: 0x07d6, // XK_Greek_PHI
+ 0x03a7: 0x07d7, // XK_Greek_CHI
+ 0x03a8: 0x07d8, // XK_Greek_PSI
+ 0x03a9: 0x07d9, // XK_Greek_OMEGA
+ 0x03aa: 0x07a5, // XK_Greek_IOTAdieresis
+ 0x03ab: 0x07a9, // XK_Greek_UPSILONdieresis
+ 0x03ac: 0x07b1, // XK_Greek_alphaaccent
+ 0x03ad: 0x07b2, // XK_Greek_epsilonaccent
+ 0x03ae: 0x07b3, // XK_Greek_etaaccent
+ 0x03af: 0x07b4, // XK_Greek_iotaaccent
+ 0x03b0: 0x07ba, // XK_Greek_upsilonaccentdieresis
+ 0x03b1: 0x07e1, // XK_Greek_alpha
+ 0x03b2: 0x07e2, // XK_Greek_beta
+ 0x03b3: 0x07e3, // XK_Greek_gamma
+ 0x03b4: 0x07e4, // XK_Greek_delta
+ 0x03b5: 0x07e5, // XK_Greek_epsilon
+ 0x03b6: 0x07e6, // XK_Greek_zeta
+ 0x03b7: 0x07e7, // XK_Greek_eta
+ 0x03b8: 0x07e8, // XK_Greek_theta
+ 0x03b9: 0x07e9, // XK_Greek_iota
+ 0x03ba: 0x07ea, // XK_Greek_kappa
+ 0x03bb: 0x07eb, // XK_Greek_lamda
+ 0x03bc: 0x07ec, // XK_Greek_mu
+ 0x03bd: 0x07ed, // XK_Greek_nu
+ 0x03be: 0x07ee, // XK_Greek_xi
+ 0x03bf: 0x07ef, // XK_Greek_omicron
+ 0x03c0: 0x07f0, // XK_Greek_pi
+ 0x03c1: 0x07f1, // XK_Greek_rho
+ 0x03c2: 0x07f3, // XK_Greek_finalsmallsigma
+ 0x03c3: 0x07f2, // XK_Greek_sigma
+ 0x03c4: 0x07f4, // XK_Greek_tau
+ 0x03c5: 0x07f5, // XK_Greek_upsilon
+ 0x03c6: 0x07f6, // XK_Greek_phi
+ 0x03c7: 0x07f7, // XK_Greek_chi
+ 0x03c8: 0x07f8, // XK_Greek_psi
+ 0x03c9: 0x07f9, // XK_Greek_omega
+ 0x03ca: 0x07b5, // XK_Greek_iotadieresis
+ 0x03cb: 0x07b9, // XK_Greek_upsilondieresis
+ 0x03cc: 0x07b7, // XK_Greek_omicronaccent
+ 0x03cd: 0x07b8, // XK_Greek_upsilonaccent
+ 0x03ce: 0x07bb, // XK_Greek_omegaaccent
+ 0x0401: 0x06b3, // XK_Cyrillic_IO
+ 0x0402: 0x06b1, // XK_Serbian_DJE
+ 0x0403: 0x06b2, // XK_Macedonia_GJE
+ 0x0404: 0x06b4, // XK_Ukrainian_IE
+ 0x0405: 0x06b5, // XK_Macedonia_DSE
+ 0x0406: 0x06b6, // XK_Ukrainian_I
+ 0x0407: 0x06b7, // XK_Ukrainian_YI
+ 0x0408: 0x06b8, // XK_Cyrillic_JE
+ 0x0409: 0x06b9, // XK_Cyrillic_LJE
+ 0x040a: 0x06ba, // XK_Cyrillic_NJE
+ 0x040b: 0x06bb, // XK_Serbian_TSHE
+ 0x040c: 0x06bc, // XK_Macedonia_KJE
+ 0x040e: 0x06be, // XK_Byelorussian_SHORTU
+ 0x040f: 0x06bf, // XK_Cyrillic_DZHE
+ 0x0410: 0x06e1, // XK_Cyrillic_A
+ 0x0411: 0x06e2, // XK_Cyrillic_BE
+ 0x0412: 0x06f7, // XK_Cyrillic_VE
+ 0x0413: 0x06e7, // XK_Cyrillic_GHE
+ 0x0414: 0x06e4, // XK_Cyrillic_DE
+ 0x0415: 0x06e5, // XK_Cyrillic_IE
+ 0x0416: 0x06f6, // XK_Cyrillic_ZHE
+ 0x0417: 0x06fa, // XK_Cyrillic_ZE
+ 0x0418: 0x06e9, // XK_Cyrillic_I
+ 0x0419: 0x06ea, // XK_Cyrillic_SHORTI
+ 0x041a: 0x06eb, // XK_Cyrillic_KA
+ 0x041b: 0x06ec, // XK_Cyrillic_EL
+ 0x041c: 0x06ed, // XK_Cyrillic_EM
+ 0x041d: 0x06ee, // XK_Cyrillic_EN
+ 0x041e: 0x06ef, // XK_Cyrillic_O
+ 0x041f: 0x06f0, // XK_Cyrillic_PE
+ 0x0420: 0x06f2, // XK_Cyrillic_ER
+ 0x0421: 0x06f3, // XK_Cyrillic_ES
+ 0x0422: 0x06f4, // XK_Cyrillic_TE
+ 0x0423: 0x06f5, // XK_Cyrillic_U
+ 0x0424: 0x06e6, // XK_Cyrillic_EF
+ 0x0425: 0x06e8, // XK_Cyrillic_HA
+ 0x0426: 0x06e3, // XK_Cyrillic_TSE
+ 0x0427: 0x06fe, // XK_Cyrillic_CHE
+ 0x0428: 0x06fb, // XK_Cyrillic_SHA
+ 0x0429: 0x06fd, // XK_Cyrillic_SHCHA
+ 0x042a: 0x06ff, // XK_Cyrillic_HARDSIGN
+ 0x042b: 0x06f9, // XK_Cyrillic_YERU
+ 0x042c: 0x06f8, // XK_Cyrillic_SOFTSIGN
+ 0x042d: 0x06fc, // XK_Cyrillic_E
+ 0x042e: 0x06e0, // XK_Cyrillic_YU
+ 0x042f: 0x06f1, // XK_Cyrillic_YA
+ 0x0430: 0x06c1, // XK_Cyrillic_a
+ 0x0431: 0x06c2, // XK_Cyrillic_be
+ 0x0432: 0x06d7, // XK_Cyrillic_ve
+ 0x0433: 0x06c7, // XK_Cyrillic_ghe
+ 0x0434: 0x06c4, // XK_Cyrillic_de
+ 0x0435: 0x06c5, // XK_Cyrillic_ie
+ 0x0436: 0x06d6, // XK_Cyrillic_zhe
+ 0x0437: 0x06da, // XK_Cyrillic_ze
+ 0x0438: 0x06c9, // XK_Cyrillic_i
+ 0x0439: 0x06ca, // XK_Cyrillic_shorti
+ 0x043a: 0x06cb, // XK_Cyrillic_ka
+ 0x043b: 0x06cc, // XK_Cyrillic_el
+ 0x043c: 0x06cd, // XK_Cyrillic_em
+ 0x043d: 0x06ce, // XK_Cyrillic_en
+ 0x043e: 0x06cf, // XK_Cyrillic_o
+ 0x043f: 0x06d0, // XK_Cyrillic_pe
+ 0x0440: 0x06d2, // XK_Cyrillic_er
+ 0x0441: 0x06d3, // XK_Cyrillic_es
+ 0x0442: 0x06d4, // XK_Cyrillic_te
+ 0x0443: 0x06d5, // XK_Cyrillic_u
+ 0x0444: 0x06c6, // XK_Cyrillic_ef
+ 0x0445: 0x06c8, // XK_Cyrillic_ha
+ 0x0446: 0x06c3, // XK_Cyrillic_tse
+ 0x0447: 0x06de, // XK_Cyrillic_che
+ 0x0448: 0x06db, // XK_Cyrillic_sha
+ 0x0449: 0x06dd, // XK_Cyrillic_shcha
+ 0x044a: 0x06df, // XK_Cyrillic_hardsign
+ 0x044b: 0x06d9, // XK_Cyrillic_yeru
+ 0x044c: 0x06d8, // XK_Cyrillic_softsign
+ 0x044d: 0x06dc, // XK_Cyrillic_e
+ 0x044e: 0x06c0, // XK_Cyrillic_yu
+ 0x044f: 0x06d1, // XK_Cyrillic_ya
+ 0x0451: 0x06a3, // XK_Cyrillic_io
+ 0x0452: 0x06a1, // XK_Serbian_dje
+ 0x0453: 0x06a2, // XK_Macedonia_gje
+ 0x0454: 0x06a4, // XK_Ukrainian_ie
+ 0x0455: 0x06a5, // XK_Macedonia_dse
+ 0x0456: 0x06a6, // XK_Ukrainian_i
+ 0x0457: 0x06a7, // XK_Ukrainian_yi
+ 0x0458: 0x06a8, // XK_Cyrillic_je
+ 0x0459: 0x06a9, // XK_Cyrillic_lje
+ 0x045a: 0x06aa, // XK_Cyrillic_nje
+ 0x045b: 0x06ab, // XK_Serbian_tshe
+ 0x045c: 0x06ac, // XK_Macedonia_kje
+ 0x045e: 0x06ae, // XK_Byelorussian_shortu
+ 0x045f: 0x06af, // XK_Cyrillic_dzhe
+ 0x0490: 0x06bd, // XK_Ukrainian_GHE_WITH_UPTURN
+ 0x0491: 0x06ad, // XK_Ukrainian_ghe_with_upturn
+ 0x05d0: 0x0ce0, // XK_hebrew_aleph
+ 0x05d1: 0x0ce1, // XK_hebrew_bet
+ 0x05d2: 0x0ce2, // XK_hebrew_gimel
+ 0x05d3: 0x0ce3, // XK_hebrew_dalet
+ 0x05d4: 0x0ce4, // XK_hebrew_he
+ 0x05d5: 0x0ce5, // XK_hebrew_waw
+ 0x05d6: 0x0ce6, // XK_hebrew_zain
+ 0x05d7: 0x0ce7, // XK_hebrew_chet
+ 0x05d8: 0x0ce8, // XK_hebrew_tet
+ 0x05d9: 0x0ce9, // XK_hebrew_yod
+ 0x05da: 0x0cea, // XK_hebrew_finalkaph
+ 0x05db: 0x0ceb, // XK_hebrew_kaph
+ 0x05dc: 0x0cec, // XK_hebrew_lamed
+ 0x05dd: 0x0ced, // XK_hebrew_finalmem
+ 0x05de: 0x0cee, // XK_hebrew_mem
+ 0x05df: 0x0cef, // XK_hebrew_finalnun
+ 0x05e0: 0x0cf0, // XK_hebrew_nun
+ 0x05e1: 0x0cf1, // XK_hebrew_samech
+ 0x05e2: 0x0cf2, // XK_hebrew_ayin
+ 0x05e3: 0x0cf3, // XK_hebrew_finalpe
+ 0x05e4: 0x0cf4, // XK_hebrew_pe
+ 0x05e5: 0x0cf5, // XK_hebrew_finalzade
+ 0x05e6: 0x0cf6, // XK_hebrew_zade
+ 0x05e7: 0x0cf7, // XK_hebrew_qoph
+ 0x05e8: 0x0cf8, // XK_hebrew_resh
+ 0x05e9: 0x0cf9, // XK_hebrew_shin
+ 0x05ea: 0x0cfa, // XK_hebrew_taw
+ 0x060c: 0x05ac, // XK_Arabic_comma
+ 0x061b: 0x05bb, // XK_Arabic_semicolon
+ 0x061f: 0x05bf, // XK_Arabic_question_mark
+ 0x0621: 0x05c1, // XK_Arabic_hamza
+ 0x0622: 0x05c2, // XK_Arabic_maddaonalef
+ 0x0623: 0x05c3, // XK_Arabic_hamzaonalef
+ 0x0624: 0x05c4, // XK_Arabic_hamzaonwaw
+ 0x0625: 0x05c5, // XK_Arabic_hamzaunderalef
+ 0x0626: 0x05c6, // XK_Arabic_hamzaonyeh
+ 0x0627: 0x05c7, // XK_Arabic_alef
+ 0x0628: 0x05c8, // XK_Arabic_beh
+ 0x0629: 0x05c9, // XK_Arabic_tehmarbuta
+ 0x062a: 0x05ca, // XK_Arabic_teh
+ 0x062b: 0x05cb, // XK_Arabic_theh
+ 0x062c: 0x05cc, // XK_Arabic_jeem
+ 0x062d: 0x05cd, // XK_Arabic_hah
+ 0x062e: 0x05ce, // XK_Arabic_khah
+ 0x062f: 0x05cf, // XK_Arabic_dal
+ 0x0630: 0x05d0, // XK_Arabic_thal
+ 0x0631: 0x05d1, // XK_Arabic_ra
+ 0x0632: 0x05d2, // XK_Arabic_zain
+ 0x0633: 0x05d3, // XK_Arabic_seen
+ 0x0634: 0x05d4, // XK_Arabic_sheen
+ 0x0635: 0x05d5, // XK_Arabic_sad
+ 0x0636: 0x05d6, // XK_Arabic_dad
+ 0x0637: 0x05d7, // XK_Arabic_tah
+ 0x0638: 0x05d8, // XK_Arabic_zah
+ 0x0639: 0x05d9, // XK_Arabic_ain
+ 0x063a: 0x05da, // XK_Arabic_ghain
+ 0x0640: 0x05e0, // XK_Arabic_tatweel
+ 0x0641: 0x05e1, // XK_Arabic_feh
+ 0x0642: 0x05e2, // XK_Arabic_qaf
+ 0x0643: 0x05e3, // XK_Arabic_kaf
+ 0x0644: 0x05e4, // XK_Arabic_lam
+ 0x0645: 0x05e5, // XK_Arabic_meem
+ 0x0646: 0x05e6, // XK_Arabic_noon
+ 0x0647: 0x05e7, // XK_Arabic_ha
+ 0x0648: 0x05e8, // XK_Arabic_waw
+ 0x0649: 0x05e9, // XK_Arabic_alefmaksura
+ 0x064a: 0x05ea, // XK_Arabic_yeh
+ 0x064b: 0x05eb, // XK_Arabic_fathatan
+ 0x064c: 0x05ec, // XK_Arabic_dammatan
+ 0x064d: 0x05ed, // XK_Arabic_kasratan
+ 0x064e: 0x05ee, // XK_Arabic_fatha
+ 0x064f: 0x05ef, // XK_Arabic_damma
+ 0x0650: 0x05f0, // XK_Arabic_kasra
+ 0x0651: 0x05f1, // XK_Arabic_shadda
+ 0x0652: 0x05f2, // XK_Arabic_sukun
+ 0x0e01: 0x0da1, // XK_Thai_kokai
+ 0x0e02: 0x0da2, // XK_Thai_khokhai
+ 0x0e03: 0x0da3, // XK_Thai_khokhuat
+ 0x0e04: 0x0da4, // XK_Thai_khokhwai
+ 0x0e05: 0x0da5, // XK_Thai_khokhon
+ 0x0e06: 0x0da6, // XK_Thai_khorakhang
+ 0x0e07: 0x0da7, // XK_Thai_ngongu
+ 0x0e08: 0x0da8, // XK_Thai_chochan
+ 0x0e09: 0x0da9, // XK_Thai_choching
+ 0x0e0a: 0x0daa, // XK_Thai_chochang
+ 0x0e0b: 0x0dab, // XK_Thai_soso
+ 0x0e0c: 0x0dac, // XK_Thai_chochoe
+ 0x0e0d: 0x0dad, // XK_Thai_yoying
+ 0x0e0e: 0x0dae, // XK_Thai_dochada
+ 0x0e0f: 0x0daf, // XK_Thai_topatak
+ 0x0e10: 0x0db0, // XK_Thai_thothan
+ 0x0e11: 0x0db1, // XK_Thai_thonangmontho
+ 0x0e12: 0x0db2, // XK_Thai_thophuthao
+ 0x0e13: 0x0db3, // XK_Thai_nonen
+ 0x0e14: 0x0db4, // XK_Thai_dodek
+ 0x0e15: 0x0db5, // XK_Thai_totao
+ 0x0e16: 0x0db6, // XK_Thai_thothung
+ 0x0e17: 0x0db7, // XK_Thai_thothahan
+ 0x0e18: 0x0db8, // XK_Thai_thothong
+ 0x0e19: 0x0db9, // XK_Thai_nonu
+ 0x0e1a: 0x0dba, // XK_Thai_bobaimai
+ 0x0e1b: 0x0dbb, // XK_Thai_popla
+ 0x0e1c: 0x0dbc, // XK_Thai_phophung
+ 0x0e1d: 0x0dbd, // XK_Thai_fofa
+ 0x0e1e: 0x0dbe, // XK_Thai_phophan
+ 0x0e1f: 0x0dbf, // XK_Thai_fofan
+ 0x0e20: 0x0dc0, // XK_Thai_phosamphao
+ 0x0e21: 0x0dc1, // XK_Thai_moma
+ 0x0e22: 0x0dc2, // XK_Thai_yoyak
+ 0x0e23: 0x0dc3, // XK_Thai_rorua
+ 0x0e24: 0x0dc4, // XK_Thai_ru
+ 0x0e25: 0x0dc5, // XK_Thai_loling
+ 0x0e26: 0x0dc6, // XK_Thai_lu
+ 0x0e27: 0x0dc7, // XK_Thai_wowaen
+ 0x0e28: 0x0dc8, // XK_Thai_sosala
+ 0x0e29: 0x0dc9, // XK_Thai_sorusi
+ 0x0e2a: 0x0dca, // XK_Thai_sosua
+ 0x0e2b: 0x0dcb, // XK_Thai_hohip
+ 0x0e2c: 0x0dcc, // XK_Thai_lochula
+ 0x0e2d: 0x0dcd, // XK_Thai_oang
+ 0x0e2e: 0x0dce, // XK_Thai_honokhuk
+ 0x0e2f: 0x0dcf, // XK_Thai_paiyannoi
+ 0x0e30: 0x0dd0, // XK_Thai_saraa
+ 0x0e31: 0x0dd1, // XK_Thai_maihanakat
+ 0x0e32: 0x0dd2, // XK_Thai_saraaa
+ 0x0e33: 0x0dd3, // XK_Thai_saraam
+ 0x0e34: 0x0dd4, // XK_Thai_sarai
+ 0x0e35: 0x0dd5, // XK_Thai_saraii
+ 0x0e36: 0x0dd6, // XK_Thai_saraue
+ 0x0e37: 0x0dd7, // XK_Thai_sarauee
+ 0x0e38: 0x0dd8, // XK_Thai_sarau
+ 0x0e39: 0x0dd9, // XK_Thai_sarauu
+ 0x0e3a: 0x0dda, // XK_Thai_phinthu
+ 0x0e3f: 0x0ddf, // XK_Thai_baht
+ 0x0e40: 0x0de0, // XK_Thai_sarae
+ 0x0e41: 0x0de1, // XK_Thai_saraae
+ 0x0e42: 0x0de2, // XK_Thai_sarao
+ 0x0e43: 0x0de3, // XK_Thai_saraaimaimuan
+ 0x0e44: 0x0de4, // XK_Thai_saraaimaimalai
+ 0x0e45: 0x0de5, // XK_Thai_lakkhangyao
+ 0x0e46: 0x0de6, // XK_Thai_maiyamok
+ 0x0e47: 0x0de7, // XK_Thai_maitaikhu
+ 0x0e48: 0x0de8, // XK_Thai_maiek
+ 0x0e49: 0x0de9, // XK_Thai_maitho
+ 0x0e4a: 0x0dea, // XK_Thai_maitri
+ 0x0e4b: 0x0deb, // XK_Thai_maichattawa
+ 0x0e4c: 0x0dec, // XK_Thai_thanthakhat
+ 0x0e4d: 0x0ded, // XK_Thai_nikhahit
+ 0x0e50: 0x0df0, // XK_Thai_leksun
+ 0x0e51: 0x0df1, // XK_Thai_leknung
+ 0x0e52: 0x0df2, // XK_Thai_leksong
+ 0x0e53: 0x0df3, // XK_Thai_leksam
+ 0x0e54: 0x0df4, // XK_Thai_leksi
+ 0x0e55: 0x0df5, // XK_Thai_lekha
+ 0x0e56: 0x0df6, // XK_Thai_lekhok
+ 0x0e57: 0x0df7, // XK_Thai_lekchet
+ 0x0e58: 0x0df8, // XK_Thai_lekpaet
+ 0x0e59: 0x0df9, // XK_Thai_lekkao
+ 0x2002: 0x0aa2, // XK_enspace
+ 0x2003: 0x0aa1, // XK_emspace
+ 0x2004: 0x0aa3, // XK_em3space
+ 0x2005: 0x0aa4, // XK_em4space
+ 0x2007: 0x0aa5, // XK_digitspace
+ 0x2008: 0x0aa6, // XK_punctspace
+ 0x2009: 0x0aa7, // XK_thinspace
+ 0x200a: 0x0aa8, // XK_hairspace
+ 0x2012: 0x0abb, // XK_figdash
+ 0x2013: 0x0aaa, // XK_endash
+ 0x2014: 0x0aa9, // XK_emdash
+ 0x2015: 0x07af, // XK_Greek_horizbar
+ 0x2017: 0x0cdf, // XK_hebrew_doublelowline
+ 0x2018: 0x0ad0, // XK_leftsinglequotemark
+ 0x2019: 0x0ad1, // XK_rightsinglequotemark
+ 0x201a: 0x0afd, // XK_singlelowquotemark
+ 0x201c: 0x0ad2, // XK_leftdoublequotemark
+ 0x201d: 0x0ad3, // XK_rightdoublequotemark
+ 0x201e: 0x0afe, // XK_doublelowquotemark
+ 0x2020: 0x0af1, // XK_dagger
+ 0x2021: 0x0af2, // XK_doubledagger
+ 0x2022: 0x0ae6, // XK_enfilledcircbullet
+ 0x2025: 0x0aaf, // XK_doubbaselinedot
+ 0x2026: 0x0aae, // XK_ellipsis
+ 0x2030: 0x0ad5, // XK_permille
+ 0x2032: 0x0ad6, // XK_minutes
+ 0x2033: 0x0ad7, // XK_seconds
+ 0x2038: 0x0afc, // XK_caret
+ 0x203e: 0x047e, // XK_overline
+ 0x20a9: 0x0eff, // XK_Korean_Won
+ 0x20ac: 0x20ac, // XK_EuroSign
+ 0x2105: 0x0ab8, // XK_careof
+ 0x2116: 0x06b0, // XK_numerosign
+ 0x2117: 0x0afb, // XK_phonographcopyright
+ 0x211e: 0x0ad4, // XK_prescription
+ 0x2122: 0x0ac9, // XK_trademark
+ 0x2153: 0x0ab0, // XK_onethird
+ 0x2154: 0x0ab1, // XK_twothirds
+ 0x2155: 0x0ab2, // XK_onefifth
+ 0x2156: 0x0ab3, // XK_twofifths
+ 0x2157: 0x0ab4, // XK_threefifths
+ 0x2158: 0x0ab5, // XK_fourfifths
+ 0x2159: 0x0ab6, // XK_onesixth
+ 0x215a: 0x0ab7, // XK_fivesixths
+ 0x215b: 0x0ac3, // XK_oneeighth
+ 0x215c: 0x0ac4, // XK_threeeighths
+ 0x215d: 0x0ac5, // XK_fiveeighths
+ 0x215e: 0x0ac6, // XK_seveneighths
+ 0x2190: 0x08fb, // XK_leftarrow
+ 0x2191: 0x08fc, // XK_uparrow
+ 0x2192: 0x08fd, // XK_rightarrow
+ 0x2193: 0x08fe, // XK_downarrow
+ 0x21d2: 0x08ce, // XK_implies
+ 0x21d4: 0x08cd, // XK_ifonlyif
+ 0x2202: 0x08ef, // XK_partialderivative
+ 0x2207: 0x08c5, // XK_nabla
+ 0x2218: 0x0bca, // XK_jot
+ 0x221a: 0x08d6, // XK_radical
+ 0x221d: 0x08c1, // XK_variation
+ 0x221e: 0x08c2, // XK_infinity
+ 0x2227: 0x08de, // XK_logicaland
+ 0x2228: 0x08df, // XK_logicalor
+ 0x2229: 0x08dc, // XK_intersection
+ 0x222a: 0x08dd, // XK_union
+ 0x222b: 0x08bf, // XK_integral
+ 0x2234: 0x08c0, // XK_therefore
+ 0x223c: 0x08c8, // XK_approximate
+ 0x2243: 0x08c9, // XK_similarequal
+ 0x2245: 0x1002248, // XK_approxeq
+ 0x2260: 0x08bd, // XK_notequal
+ 0x2261: 0x08cf, // XK_identical
+ 0x2264: 0x08bc, // XK_lessthanequal
+ 0x2265: 0x08be, // XK_greaterthanequal
+ 0x2282: 0x08da, // XK_includedin
+ 0x2283: 0x08db, // XK_includes
+ 0x22a2: 0x0bfc, // XK_righttack
+ 0x22a3: 0x0bdc, // XK_lefttack
+ 0x22a4: 0x0bc2, // XK_downtack
+ 0x22a5: 0x0bce, // XK_uptack
+ 0x2308: 0x0bd3, // XK_upstile
+ 0x230a: 0x0bc4, // XK_downstile
+ 0x2315: 0x0afa, // XK_telephonerecorder
+ 0x2320: 0x08a4, // XK_topintegral
+ 0x2321: 0x08a5, // XK_botintegral
+ 0x2395: 0x0bcc, // XK_quad
+ 0x239b: 0x08ab, // XK_topleftparens
+ 0x239d: 0x08ac, // XK_botleftparens
+ 0x239e: 0x08ad, // XK_toprightparens
+ 0x23a0: 0x08ae, // XK_botrightparens
+ 0x23a1: 0x08a7, // XK_topleftsqbracket
+ 0x23a3: 0x08a8, // XK_botleftsqbracket
+ 0x23a4: 0x08a9, // XK_toprightsqbracket
+ 0x23a6: 0x08aa, // XK_botrightsqbracket
+ 0x23a8: 0x08af, // XK_leftmiddlecurlybrace
+ 0x23ac: 0x08b0, // XK_rightmiddlecurlybrace
+ 0x23b7: 0x08a1, // XK_leftradical
+ 0x23ba: 0x09ef, // XK_horizlinescan1
+ 0x23bb: 0x09f0, // XK_horizlinescan3
+ 0x23bc: 0x09f2, // XK_horizlinescan7
+ 0x23bd: 0x09f3, // XK_horizlinescan9
+ 0x2409: 0x09e2, // XK_ht
+ 0x240a: 0x09e5, // XK_lf
+ 0x240b: 0x09e9, // XK_vt
+ 0x240c: 0x09e3, // XK_ff
+ 0x240d: 0x09e4, // XK_cr
+ 0x2423: 0x0aac, // XK_signifblank
+ 0x2424: 0x09e8, // XK_nl
+ 0x2500: 0x08a3, // XK_horizconnector
+ 0x2502: 0x08a6, // XK_vertconnector
+ 0x250c: 0x08a2, // XK_topleftradical
+ 0x2510: 0x09eb, // XK_uprightcorner
+ 0x2514: 0x09ed, // XK_lowleftcorner
+ 0x2518: 0x09ea, // XK_lowrightcorner
+ 0x251c: 0x09f4, // XK_leftt
+ 0x2524: 0x09f5, // XK_rightt
+ 0x252c: 0x09f7, // XK_topt
+ 0x2534: 0x09f6, // XK_bott
+ 0x253c: 0x09ee, // XK_crossinglines
+ 0x2592: 0x09e1, // XK_checkerboard
+ 0x25aa: 0x0ae7, // XK_enfilledsqbullet
+ 0x25ab: 0x0ae1, // XK_enopensquarebullet
+ 0x25ac: 0x0adb, // XK_filledrectbullet
+ 0x25ad: 0x0ae2, // XK_openrectbullet
+ 0x25ae: 0x0adf, // XK_emfilledrect
+ 0x25af: 0x0acf, // XK_emopenrectangle
+ 0x25b2: 0x0ae8, // XK_filledtribulletup
+ 0x25b3: 0x0ae3, // XK_opentribulletup
+ 0x25b6: 0x0add, // XK_filledrighttribullet
+ 0x25b7: 0x0acd, // XK_rightopentriangle
+ 0x25bc: 0x0ae9, // XK_filledtribulletdown
+ 0x25bd: 0x0ae4, // XK_opentribulletdown
+ 0x25c0: 0x0adc, // XK_filledlefttribullet
+ 0x25c1: 0x0acc, // XK_leftopentriangle
+ 0x25c6: 0x09e0, // XK_soliddiamond
+ 0x25cb: 0x0ace, // XK_emopencircle
+ 0x25cf: 0x0ade, // XK_emfilledcircle
+ 0x25e6: 0x0ae0, // XK_enopencircbullet
+ 0x2606: 0x0ae5, // XK_openstar
+ 0x260e: 0x0af9, // XK_telephone
+ 0x2613: 0x0aca, // XK_signaturemark
+ 0x261c: 0x0aea, // XK_leftpointer
+ 0x261e: 0x0aeb, // XK_rightpointer
+ 0x2640: 0x0af8, // XK_femalesymbol
+ 0x2642: 0x0af7, // XK_malesymbol
+ 0x2663: 0x0aec, // XK_club
+ 0x2665: 0x0aee, // XK_heart
+ 0x2666: 0x0aed, // XK_diamond
+ 0x266d: 0x0af6, // XK_musicalflat
+ 0x266f: 0x0af5, // XK_musicalsharp
+ 0x2713: 0x0af3, // XK_checkmark
+ 0x2717: 0x0af4, // XK_ballotcross
+ 0x271d: 0x0ad9, // XK_latincross
+ 0x2720: 0x0af0, // XK_maltesecross
+ 0x27e8: 0x0abc, // XK_leftanglebracket
+ 0x27e9: 0x0abe, // XK_rightanglebracket
+ 0x3001: 0x04a4, // XK_kana_comma
+ 0x3002: 0x04a1, // XK_kana_fullstop
+ 0x300c: 0x04a2, // XK_kana_openingbracket
+ 0x300d: 0x04a3, // XK_kana_closingbracket
+ 0x309b: 0x04de, // XK_voicedsound
+ 0x309c: 0x04df, // XK_semivoicedsound
+ 0x30a1: 0x04a7, // XK_kana_a
+ 0x30a2: 0x04b1, // XK_kana_A
+ 0x30a3: 0x04a8, // XK_kana_i
+ 0x30a4: 0x04b2, // XK_kana_I
+ 0x30a5: 0x04a9, // XK_kana_u
+ 0x30a6: 0x04b3, // XK_kana_U
+ 0x30a7: 0x04aa, // XK_kana_e
+ 0x30a8: 0x04b4, // XK_kana_E
+ 0x30a9: 0x04ab, // XK_kana_o
+ 0x30aa: 0x04b5, // XK_kana_O
+ 0x30ab: 0x04b6, // XK_kana_KA
+ 0x30ad: 0x04b7, // XK_kana_KI
+ 0x30af: 0x04b8, // XK_kana_KU
+ 0x30b1: 0x04b9, // XK_kana_KE
+ 0x30b3: 0x04ba, // XK_kana_KO
+ 0x30b5: 0x04bb, // XK_kana_SA
+ 0x30b7: 0x04bc, // XK_kana_SHI
+ 0x30b9: 0x04bd, // XK_kana_SU
+ 0x30bb: 0x04be, // XK_kana_SE
+ 0x30bd: 0x04bf, // XK_kana_SO
+ 0x30bf: 0x04c0, // XK_kana_TA
+ 0x30c1: 0x04c1, // XK_kana_CHI
+ 0x30c3: 0x04af, // XK_kana_tsu
+ 0x30c4: 0x04c2, // XK_kana_TSU
+ 0x30c6: 0x04c3, // XK_kana_TE
+ 0x30c8: 0x04c4, // XK_kana_TO
+ 0x30ca: 0x04c5, // XK_kana_NA
+ 0x30cb: 0x04c6, // XK_kana_NI
+ 0x30cc: 0x04c7, // XK_kana_NU
+ 0x30cd: 0x04c8, // XK_kana_NE
+ 0x30ce: 0x04c9, // XK_kana_NO
+ 0x30cf: 0x04ca, // XK_kana_HA
+ 0x30d2: 0x04cb, // XK_kana_HI
+ 0x30d5: 0x04cc, // XK_kana_FU
+ 0x30d8: 0x04cd, // XK_kana_HE
+ 0x30db: 0x04ce, // XK_kana_HO
+ 0x30de: 0x04cf, // XK_kana_MA
+ 0x30df: 0x04d0, // XK_kana_MI
+ 0x30e0: 0x04d1, // XK_kana_MU
+ 0x30e1: 0x04d2, // XK_kana_ME
+ 0x30e2: 0x04d3, // XK_kana_MO
+ 0x30e3: 0x04ac, // XK_kana_ya
+ 0x30e4: 0x04d4, // XK_kana_YA
+ 0x30e5: 0x04ad, // XK_kana_yu
+ 0x30e6: 0x04d5, // XK_kana_YU
+ 0x30e7: 0x04ae, // XK_kana_yo
+ 0x30e8: 0x04d6, // XK_kana_YO
+ 0x30e9: 0x04d7, // XK_kana_RA
+ 0x30ea: 0x04d8, // XK_kana_RI
+ 0x30eb: 0x04d9, // XK_kana_RU
+ 0x30ec: 0x04da, // XK_kana_RE
+ 0x30ed: 0x04db, // XK_kana_RO
+ 0x30ef: 0x04dc, // XK_kana_WA
+ 0x30f2: 0x04a6, // XK_kana_WO
+ 0x30f3: 0x04dd, // XK_kana_N
+ 0x30fb: 0x04a5, // XK_kana_conjunctive
+ 0x30fc: 0x04b0, // XK_prolongedsound
+};
+
+export default {
+ lookup(u) {
+ // Latin-1 is one-to-one mapping
+ if ((u >= 0x20) && (u <= 0xff)) {
+ return u;
+ }
+
+ // Lookup table (fairly random)
+ const keysym = codepoints[u];
+ if (keysym !== undefined) {
+ return keysym;
+ }
+
+ // General mapping as final fallback
+ return 0x01000000 | u;
+ },
+};
diff --git a/systemvm/agent/noVNC/core/input/mouse.js b/systemvm/agent/noVNC/core/input/mouse.js
new file mode 100644
index 0000000..58a2982
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/mouse.js
@@ -0,0 +1,276 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+import * as Log from '../util/logging.js';
+import { isTouchDevice } from '../util/browser.js';
+import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
+
+const WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
+const WHEEL_STEP_TIMEOUT = 50; // ms
+const WHEEL_LINE_HEIGHT = 19;
+
+export default class Mouse {
+ constructor(target) {
+ this._target = target || document;
+
+ this._doubleClickTimer = null;
+ this._lastTouchPos = null;
+
+ this._pos = null;
+ this._wheelStepXTimer = null;
+ this._wheelStepYTimer = null;
+ this._accumulatedWheelDeltaX = 0;
+ this._accumulatedWheelDeltaY = 0;
+
+ this._eventHandlers = {
+ 'mousedown': this._handleMouseDown.bind(this),
+ 'mouseup': this._handleMouseUp.bind(this),
+ 'mousemove': this._handleMouseMove.bind(this),
+ 'mousewheel': this._handleMouseWheel.bind(this),
+ 'mousedisable': this._handleMouseDisable.bind(this)
+ };
+
+ // ===== PROPERTIES =====
+
+ this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
+
+ // ===== EVENT HANDLERS =====
+
+ this.onmousebutton = () => {}; // Handler for mouse button click/release
+ this.onmousemove = () => {}; // Handler for mouse movement
+ }
+
+ // ===== PRIVATE METHODS =====
+
+ _resetDoubleClickTimer() {
+ this._doubleClickTimer = null;
+ }
+
+ _handleMouseButton(e, down) {
+ this._updateMousePosition(e);
+ let pos = this._pos;
+
+ let bmask;
+ if (e.touches || e.changedTouches) {
+ // Touch device
+
+ // When two touches occur within 500 ms of each other and are
+ // close enough together a double click is triggered.
+ if (down == 1) {
+ if (this._doubleClickTimer === null) {
+ this._lastTouchPos = pos;
+ } else {
+ clearTimeout(this._doubleClickTimer);
+
+ // When the distance between the two touches is small enough
+ // force the position of the latter touch to the position of
+ // the first.
+
+ const xs = this._lastTouchPos.x - pos.x;
+ const ys = this._lastTouchPos.y - pos.y;
+ const d = Math.sqrt((xs * xs) + (ys * ys));
+
+ // The goal is to trigger on a certain physical width, the
+ // devicePixelRatio brings us a bit closer but is not optimal.
+ const threshold = 20 * (window.devicePixelRatio || 1);
+ if (d < threshold) {
+ pos = this._lastTouchPos;
+ }
+ }
+ this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
+ }
+ bmask = this.touchButton;
+ // If bmask is set
+ } else if (e.which) {
+ /* everything except IE */
+ bmask = 1 << e.button;
+ } else {
+ /* IE including 9 */
+ bmask = (e.button & 0x1) + // Left
+ (e.button & 0x2) * 2 + // Right
+ (e.button & 0x4) / 2; // Middle
+ }
+
+ Log.Debug("onmousebutton " + (down ? "down" : "up") +
+ ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
+ this.onmousebutton(pos.x, pos.y, down, bmask);
+
+ stopEvent(e);
+ }
+
+ _handleMouseDown(e) {
+ // Touch events have implicit capture
+ if (e.type === "mousedown") {
+ setCapture(this._target);
+ }
+
+ this._handleMouseButton(e, 1);
+ }
+
+ _handleMouseUp(e) {
+ this._handleMouseButton(e, 0);
+ }
+
+ // Mouse wheel events are sent in steps over VNC. This means that the VNC
+ // protocol can't handle a wheel event with specific distance or speed.
+ // Therefor, if we get a lot of small mouse wheel events we combine them.
+ _generateWheelStepX() {
+
+ if (this._accumulatedWheelDeltaX < 0) {
+ this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
+ this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
+ } else if (this._accumulatedWheelDeltaX > 0) {
+ this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
+ this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
+ }
+
+ this._accumulatedWheelDeltaX = 0;
+ }
+
+ _generateWheelStepY() {
+
+ if (this._accumulatedWheelDeltaY < 0) {
+ this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
+ this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
+ } else if (this._accumulatedWheelDeltaY > 0) {
+ this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
+ this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
+ }
+
+ this._accumulatedWheelDeltaY = 0;
+ }
+
+ _resetWheelStepTimers() {
+ window.clearTimeout(this._wheelStepXTimer);
+ window.clearTimeout(this._wheelStepYTimer);
+ this._wheelStepXTimer = null;
+ this._wheelStepYTimer = null;
+ }
+
+ _handleMouseWheel(e) {
+ this._resetWheelStepTimers();
+
+ this._updateMousePosition(e);
+
+ let dX = e.deltaX;
+ let dY = e.deltaY;
+
+ // Pixel units unless it's non-zero.
+ // Note that if deltamode is line or page won't matter since we aren't
+ // sending the mouse wheel delta to the server anyway.
+ // The difference between pixel and line can be important however since
+ // we have a threshold that can be smaller than the line height.
+ if (e.deltaMode !== 0) {
+ dX *= WHEEL_LINE_HEIGHT;
+ dY *= WHEEL_LINE_HEIGHT;
+ }
+
+ this._accumulatedWheelDeltaX += dX;
+ this._accumulatedWheelDeltaY += dY;
+
+ // Generate a mouse wheel step event when the accumulated delta
+ // for one of the axes is large enough.
+ // Small delta events that do not pass the threshold get sent
+ // after a timeout.
+ if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
+ this._generateWheelStepX();
+ } else {
+ this._wheelStepXTimer =
+ window.setTimeout(this._generateWheelStepX.bind(this),
+ WHEEL_STEP_TIMEOUT);
+ }
+ if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
+ this._generateWheelStepY();
+ } else {
+ this._wheelStepYTimer =
+ window.setTimeout(this._generateWheelStepY.bind(this),
+ WHEEL_STEP_TIMEOUT);
+ }
+
+ stopEvent(e);
+ }
+
+ _handleMouseMove(e) {
+ this._updateMousePosition(e);
+ this.onmousemove(this._pos.x, this._pos.y);
+ stopEvent(e);
+ }
+
+ _handleMouseDisable(e) {
+ /*
+ * Stop propagation if inside canvas area
+ * Note: This is only needed for the 'click' event as it fails
+ * to fire properly for the target element so we have
+ * to listen on the document element instead.
+ */
+ if (e.target == this._target) {
+ stopEvent(e);
+ }
+ }
+
+ // Update coordinates relative to target
+ _updateMousePosition(e) {
+ e = getPointerEvent(e);
+ const bounds = this._target.getBoundingClientRect();
+ let x;
+ let y;
+ // Clip to target bounds
+ if (e.clientX < bounds.left) {
+ x = 0;
+ } else if (e.clientX >= bounds.right) {
+ x = bounds.width - 1;
+ } else {
+ x = e.clientX - bounds.left;
+ }
+ if (e.clientY < bounds.top) {
+ y = 0;
+ } else if (e.clientY >= bounds.bottom) {
+ y = bounds.height - 1;
+ } else {
+ y = e.clientY - bounds.top;
+ }
+ this._pos = {x: x, y: y};
+ }
+
+ // ===== PUBLIC METHODS =====
+
+ grab() {
+ if (isTouchDevice) {
+ this._target.addEventListener('touchstart', this._eventHandlers.mousedown);
+ this._target.addEventListener('touchend', this._eventHandlers.mouseup);
+ this._target.addEventListener('touchmove', this._eventHandlers.mousemove);
+ }
+ this._target.addEventListener('mousedown', this._eventHandlers.mousedown);
+ this._target.addEventListener('mouseup', this._eventHandlers.mouseup);
+ this._target.addEventListener('mousemove', this._eventHandlers.mousemove);
+ this._target.addEventListener('wheel', this._eventHandlers.mousewheel);
+
+ /* Prevent middle-click pasting (see above for why we bind to document) */
+ document.addEventListener('click', this._eventHandlers.mousedisable);
+
+ /* preventDefault() on mousedown doesn't stop this event for some
+ reason so we have to explicitly block it */
+ this._target.addEventListener('contextmenu', this._eventHandlers.mousedisable);
+ }
+
+ ungrab() {
+ this._resetWheelStepTimers();
+
+ if (isTouchDevice) {
+ this._target.removeEventListener('touchstart', this._eventHandlers.mousedown);
+ this._target.removeEventListener('touchend', this._eventHandlers.mouseup);
+ this._target.removeEventListener('touchmove', this._eventHandlers.mousemove);
+ }
+ this._target.removeEventListener('mousedown', this._eventHandlers.mousedown);
+ this._target.removeEventListener('mouseup', this._eventHandlers.mouseup);
+ this._target.removeEventListener('mousemove', this._eventHandlers.mousemove);
+ this._target.removeEventListener('wheel', this._eventHandlers.mousewheel);
+
+ document.removeEventListener('click', this._eventHandlers.mousedisable);
+
+ this._target.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
+ }
+}
diff --git a/systemvm/agent/noVNC/core/input/util.js b/systemvm/agent/noVNC/core/input/util.js
new file mode 100644
index 0000000..f177ef5
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/util.js
@@ -0,0 +1,164 @@
+import keysyms from "./keysymdef.js";
+import vkeys from "./vkeys.js";
+import fixedkeys from "./fixedkeys.js";
+import DOMKeyTable from "./domkeytable.js";
+import * as browser from "../util/browser.js";
+
+// Get 'KeyboardEvent.code', handling legacy browsers
+export function getKeycode(evt) {
+ // Are we getting proper key identifiers?
+ // (unfortunately Firefox and Chrome are crappy here and gives
+ // us an empty string on some platforms, rather than leaving it
+ // undefined)
+ if (evt.code) {
+ // Mozilla isn't fully in sync with the spec yet
+ switch (evt.code) {
+ case 'OSLeft': return 'MetaLeft';
+ case 'OSRight': return 'MetaRight';
+ }
+
+ return evt.code;
+ }
+
+ // The de-facto standard is to use Windows Virtual-Key codes
+ // in the 'keyCode' field for non-printable characters. However
+ // Webkit sets it to the same as charCode in 'keypress' events.
+ if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
+ let code = vkeys[evt.keyCode];
+
+ // macOS has messed up this code for some reason
+ if (browser.isMac() && (code === 'ContextMenu')) {
+ code = 'MetaRight';
+ }
+
+ // The keyCode doesn't distinguish between left and right
+ // for the standard modifiers
+ if (evt.location === 2) {
+ switch (code) {
+ case 'ShiftLeft': return 'ShiftRight';
+ case 'ControlLeft': return 'ControlRight';
+ case 'AltLeft': return 'AltRight';
+ }
+ }
+
+ // Nor a bunch of the numpad keys
+ if (evt.location === 3) {
+ switch (code) {
+ case 'Delete': return 'NumpadDecimal';
+ case 'Insert': return 'Numpad0';
+ case 'End': return 'Numpad1';
+ case 'ArrowDown': return 'Numpad2';
+ case 'PageDown': return 'Numpad3';
+ case 'ArrowLeft': return 'Numpad4';
+ case 'ArrowRight': return 'Numpad6';
+ case 'Home': return 'Numpad7';
+ case 'ArrowUp': return 'Numpad8';
+ case 'PageUp': return 'Numpad9';
+ case 'Enter': return 'NumpadEnter';
+ }
+ }
+
+ return code;
+ }
+
+ return 'Unidentified';
+}
+
+// Get 'KeyboardEvent.key', handling legacy browsers
+export function getKey(evt) {
+ // Are we getting a proper key value?
+ if (evt.key !== undefined) {
+ // IE and Edge use some ancient version of the spec
+ // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
+ switch (evt.key) {
+ case 'Spacebar': return ' ';
+ case 'Esc': return 'Escape';
+ case 'Scroll': return 'ScrollLock';
+ case 'Win': return 'Meta';
+ case 'Apps': return 'ContextMenu';
+ case 'Up': return 'ArrowUp';
+ case 'Left': return 'ArrowLeft';
+ case 'Right': return 'ArrowRight';
+ case 'Down': return 'ArrowDown';
+ case 'Del': return 'Delete';
+ case 'Divide': return '/';
+ case 'Multiply': return '*';
+ case 'Subtract': return '-';
+ case 'Add': return '+';
+ case 'Decimal': return evt.char;
+ }
+
+ // Mozilla isn't fully in sync with the spec yet
+ switch (evt.key) {
+ case 'OS': return 'Meta';
+ }
+
+ // iOS leaks some OS names
+ switch (evt.key) {
+ case 'UIKeyInputUpArrow': return 'ArrowUp';
+ case 'UIKeyInputDownArrow': return 'ArrowDown';
+ case 'UIKeyInputLeftArrow': return 'ArrowLeft';
+ case 'UIKeyInputRightArrow': return 'ArrowRight';
+ case 'UIKeyInputEscape': return 'Escape';
+ }
+
+ // IE and Edge have broken handling of AltGraph so we cannot
+ // trust them for printable characters
+ if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
+ return evt.key;
+ }
+ }
+
+ // Try to deduce it based on the physical key
+ const code = getKeycode(evt);
+ if (code in fixedkeys) {
+ return fixedkeys[code];
+ }
+
+ // If that failed, then see if we have a printable character
+ if (evt.charCode) {
+ return String.fromCharCode(evt.charCode);
+ }
+
+ // At this point we have nothing left to go on
+ return 'Unidentified';
+}
+
+// Get the most reliable keysym value we can get from a key event
+export function getKeysym(evt) {
+ const key = getKey(evt);
+
+ if (key === 'Unidentified') {
+ return null;
+ }
+
+ // First look up special keys
+ if (key in DOMKeyTable) {
+ let location = evt.location;
+
+ // Safari screws up location for the right cmd key
+ if ((key === 'Meta') && (location === 0)) {
+ location = 2;
+ }
+
+ if ((location === undefined) || (location > 3)) {
+ location = 0;
+ }
+
+ return DOMKeyTable[key][location];
+ }
+
+ // Now we need to look at the Unicode symbol instead
+
+ // Special key? (FIXME: Should have been caught earlier)
+ if (key.length !== 1) {
+ return null;
+ }
+
+ const codepoint = key.charCodeAt();
+ if (codepoint) {
+ return keysyms.lookup(codepoint);
+ }
+
+ return null;
+}
diff --git a/systemvm/agent/noVNC/core/input/vkeys.js b/systemvm/agent/noVNC/core/input/vkeys.js
new file mode 100644
index 0000000..f84109b
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/vkeys.js
@@ -0,0 +1,117 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*
+ * Mapping between Microsoft® Windows® Virtual-Key codes and
+ * HTML key codes.
+ */
+
+export default {
+ 0x08: 'Backspace',
+ 0x09: 'Tab',
+ 0x0a: 'NumpadClear',
+ 0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
+ 0x0d: 'Enter',
+ 0x10: 'ShiftLeft',
+ 0x11: 'ControlLeft',
+ 0x12: 'AltLeft',
+ 0x13: 'Pause',
+ 0x14: 'CapsLock',
+ 0x15: 'Lang1',
+ 0x19: 'Lang2',
+ 0x1b: 'Escape',
+ 0x1c: 'Convert',
+ 0x1d: 'NonConvert',
+ 0x20: 'Space',
+ 0x21: 'PageUp',
+ 0x22: 'PageDown',
+ 0x23: 'End',
+ 0x24: 'Home',
+ 0x25: 'ArrowLeft',
+ 0x26: 'ArrowUp',
+ 0x27: 'ArrowRight',
+ 0x28: 'ArrowDown',
+ 0x29: 'Select',
+ 0x2c: 'PrintScreen',
+ 0x2d: 'Insert',
+ 0x2e: 'Delete',
+ 0x2f: 'Help',
+ 0x30: 'Digit0',
+ 0x31: 'Digit1',
+ 0x32: 'Digit2',
+ 0x33: 'Digit3',
+ 0x34: 'Digit4',
+ 0x35: 'Digit5',
+ 0x36: 'Digit6',
+ 0x37: 'Digit7',
+ 0x38: 'Digit8',
+ 0x39: 'Digit9',
+ 0x5b: 'MetaLeft',
+ 0x5c: 'MetaRight',
+ 0x5d: 'ContextMenu',
+ 0x5f: 'Sleep',
+ 0x60: 'Numpad0',
+ 0x61: 'Numpad1',
+ 0x62: 'Numpad2',
+ 0x63: 'Numpad3',
+ 0x64: 'Numpad4',
+ 0x65: 'Numpad5',
+ 0x66: 'Numpad6',
+ 0x67: 'Numpad7',
+ 0x68: 'Numpad8',
+ 0x69: 'Numpad9',
+ 0x6a: 'NumpadMultiply',
+ 0x6b: 'NumpadAdd',
+ 0x6c: 'NumpadDecimal',
+ 0x6d: 'NumpadSubtract',
+ 0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows
+ 0x6f: 'NumpadDivide',
+ 0x70: 'F1',
+ 0x71: 'F2',
+ 0x72: 'F3',
+ 0x73: 'F4',
+ 0x74: 'F5',
+ 0x75: 'F6',
+ 0x76: 'F7',
+ 0x77: 'F8',
+ 0x78: 'F9',
+ 0x79: 'F10',
+ 0x7a: 'F11',
+ 0x7b: 'F12',
+ 0x7c: 'F13',
+ 0x7d: 'F14',
+ 0x7e: 'F15',
+ 0x7f: 'F16',
+ 0x80: 'F17',
+ 0x81: 'F18',
+ 0x82: 'F19',
+ 0x83: 'F20',
+ 0x84: 'F21',
+ 0x85: 'F22',
+ 0x86: 'F23',
+ 0x87: 'F24',
+ 0x90: 'NumLock',
+ 0x91: 'ScrollLock',
+ 0xa6: 'BrowserBack',
+ 0xa7: 'BrowserForward',
+ 0xa8: 'BrowserRefresh',
+ 0xa9: 'BrowserStop',
+ 0xaa: 'BrowserSearch',
+ 0xab: 'BrowserFavorites',
+ 0xac: 'BrowserHome',
+ 0xad: 'AudioVolumeMute',
+ 0xae: 'AudioVolumeDown',
+ 0xaf: 'AudioVolumeUp',
+ 0xb0: 'MediaTrackNext',
+ 0xb1: 'MediaTrackPrevious',
+ 0xb2: 'MediaStop',
+ 0xb3: 'MediaPlayPause',
+ 0xb4: 'LaunchMail',
+ 0xb5: 'MediaSelect',
+ 0xb6: 'LaunchApp1',
+ 0xb7: 'LaunchApp2',
+ 0xe1: 'AltRight', // Only when it is AltGraph
+};
diff --git a/systemvm/agent/noVNC/core/input/xtscancodes.js b/systemvm/agent/noVNC/core/input/xtscancodes.js
new file mode 100644
index 0000000..514809c
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/xtscancodes.js
@@ -0,0 +1,171 @@
+/*
+ * This file is auto-generated from keymaps.csv on 2017-05-31 16:20
+ * Database checksum sha256(92fd165507f2a3b8c5b3fa56e425d45788dbcb98cf067a307527d91ce22cab94)
+ * To re-generate, run:
+ * keymap-gen --lang=js code-map keymaps.csv html atset1
+*/
+export default {
+ "Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
+ "AltLeft": 0x38, /* html:AltLeft (AltLeft) -> linux:56 (KEY_LEFTALT) -> atset1:56 */
+ "AltRight": 0xe038, /* html:AltRight (AltRight) -> linux:100 (KEY_RIGHTALT) -> atset1:57400 */
+ "ArrowDown": 0xe050, /* html:ArrowDown (ArrowDown) -> linux:108 (KEY_DOWN) -> atset1:57424 */
+ "ArrowLeft": 0xe04b, /* html:ArrowLeft (ArrowLeft) -> linux:105 (KEY_LEFT) -> atset1:57419 */
+ "ArrowRight": 0xe04d, /* html:ArrowRight (ArrowRight) -> linux:106 (KEY_RIGHT) -> atset1:57421 */
+ "ArrowUp": 0xe048, /* html:ArrowUp (ArrowUp) -> linux:103 (KEY_UP) -> atset1:57416 */
+ "AudioVolumeDown": 0xe02e, /* html:AudioVolumeDown (AudioVolumeDown) -> linux:114 (KEY_VOLUMEDOWN) -> atset1:57390 */
+ "AudioVolumeMute": 0xe020, /* html:AudioVolumeMute (AudioVolumeMute) -> linux:113 (KEY_MUTE) -> atset1:57376 */
+ "AudioVolumeUp": 0xe030, /* html:AudioVolumeUp (AudioVolumeUp) -> linux:115 (KEY_VOLUMEUP) -> atset1:57392 */
+ "Backquote": 0x29, /* html:Backquote (Backquote) -> linux:41 (KEY_GRAVE) -> atset1:41 */
+ "Backslash": 0x2b, /* html:Backslash (Backslash) -> linux:43 (KEY_BACKSLASH) -> atset1:43 */
+ "Backspace": 0xe, /* html:Backspace (Backspace) -> linux:14 (KEY_BACKSPACE) -> atset1:14 */
+ "BracketLeft": 0x1a, /* html:BracketLeft (BracketLeft) -> linux:26 (KEY_LEFTBRACE) -> atset1:26 */
+ "BracketRight": 0x1b, /* html:BracketRight (BracketRight) -> linux:27 (KEY_RIGHTBRACE) -> atset1:27 */
+ "BrowserBack": 0xe06a, /* html:BrowserBack (BrowserBack) -> linux:158 (KEY_BACK) -> atset1:57450 */
+ "BrowserFavorites": 0xe066, /* html:BrowserFavorites (BrowserFavorites) -> linux:156 (KEY_BOOKMARKS) -> atset1:57446 */
+ "BrowserForward": 0xe069, /* html:BrowserForward (BrowserForward) -> linux:159 (KEY_FORWARD) -> atset1:57449 */
+ "BrowserHome": 0xe032, /* html:BrowserHome (BrowserHome) -> linux:172 (KEY_HOMEPAGE) -> atset1:57394 */
+ "BrowserRefresh": 0xe067, /* html:BrowserRefresh (BrowserRefresh) -> linux:173 (KEY_REFRESH) -> atset1:57447 */
+ "BrowserSearch": 0xe065, /* html:BrowserSearch (BrowserSearch) -> linux:217 (KEY_SEARCH) -> atset1:57445 */
+ "BrowserStop": 0xe068, /* html:BrowserStop (BrowserStop) -> linux:128 (KEY_STOP) -> atset1:57448 */
+ "CapsLock": 0x3a, /* html:CapsLock (CapsLock) -> linux:58 (KEY_CAPSLOCK) -> atset1:58 */
+ "Comma": 0x33, /* html:Comma (Comma) -> linux:51 (KEY_COMMA) -> atset1:51 */
+ "ContextMenu": 0xe05d, /* html:ContextMenu (ContextMenu) -> linux:127 (KEY_COMPOSE) -> atset1:57437 */
+ "ControlLeft": 0x1d, /* html:ControlLeft (ControlLeft) -> linux:29 (KEY_LEFTCTRL) -> atset1:29 */
+ "ControlRight": 0xe01d, /* html:ControlRight (ControlRight) -> linux:97 (KEY_RIGHTCTRL) -> atset1:57373 */
+ "Convert": 0x79, /* html:Convert (Convert) -> linux:92 (KEY_HENKAN) -> atset1:121 */
+ "Copy": 0xe078, /* html:Copy (Copy) -> linux:133 (KEY_COPY) -> atset1:57464 */
+ "Cut": 0xe03c, /* html:Cut (Cut) -> linux:137 (KEY_CUT) -> atset1:57404 */
+ "Delete": 0xe053, /* html:Delete (Delete) -> linux:111 (KEY_DELETE) -> atset1:57427 */
+ "Digit0": 0xb, /* html:Digit0 (Digit0) -> linux:11 (KEY_0) -> atset1:11 */
+ "Digit1": 0x2, /* html:Digit1 (Digit1) -> linux:2 (KEY_1) -> atset1:2 */
+ "Digit2": 0x3, /* html:Digit2 (Digit2) -> linux:3 (KEY_2) -> atset1:3 */
+ "Digit3": 0x4, /* html:Digit3 (Digit3) -> linux:4 (KEY_3) -> atset1:4 */
+ "Digit4": 0x5, /* html:Digit4 (Digit4) -> linux:5 (KEY_4) -> atset1:5 */
+ "Digit5": 0x6, /* html:Digit5 (Digit5) -> linux:6 (KEY_5) -> atset1:6 */
+ "Digit6": 0x7, /* html:Digit6 (Digit6) -> linux:7 (KEY_6) -> atset1:7 */
+ "Digit7": 0x8, /* html:Digit7 (Digit7) -> linux:8 (KEY_7) -> atset1:8 */
+ "Digit8": 0x9, /* html:Digit8 (Digit8) -> linux:9 (KEY_8) -> atset1:9 */
+ "Digit9": 0xa, /* html:Digit9 (Digit9) -> linux:10 (KEY_9) -> atset1:10 */
+ "Eject": 0xe07d, /* html:Eject (Eject) -> linux:162 (KEY_EJECTCLOSECD) -> atset1:57469 */
+ "End": 0xe04f, /* html:End (End) -> linux:107 (KEY_END) -> atset1:57423 */
+ "Enter": 0x1c, /* html:Enter (Enter) -> linux:28 (KEY_ENTER) -> atset1:28 */
+ "Equal": 0xd, /* html:Equal (Equal) -> linux:13 (KEY_EQUAL) -> atset1:13 */
+ "Escape": 0x1, /* html:Escape (Escape) -> linux:1 (KEY_ESC) -> atset1:1 */
+ "F1": 0x3b, /* html:F1 (F1) -> linux:59 (KEY_F1) -> atset1:59 */
+ "F10": 0x44, /* html:F10 (F10) -> linux:68 (KEY_F10) -> atset1:68 */
+ "F11": 0x57, /* html:F11 (F11) -> linux:87 (KEY_F11) -> atset1:87 */
+ "F12": 0x58, /* html:F12 (F12) -> linux:88 (KEY_F12) -> atset1:88 */
+ "F13": 0x5d, /* html:F13 (F13) -> linux:183 (KEY_F13) -> atset1:93 */
+ "F14": 0x5e, /* html:F14 (F14) -> linux:184 (KEY_F14) -> atset1:94 */
+ "F15": 0x5f, /* html:F15 (F15) -> linux:185 (KEY_F15) -> atset1:95 */
+ "F16": 0x55, /* html:F16 (F16) -> linux:186 (KEY_F16) -> atset1:85 */
+ "F17": 0xe003, /* html:F17 (F17) -> linux:187 (KEY_F17) -> atset1:57347 */
+ "F18": 0xe077, /* html:F18 (F18) -> linux:188 (KEY_F18) -> atset1:57463 */
+ "F19": 0xe004, /* html:F19 (F19) -> linux:189 (KEY_F19) -> atset1:57348 */
+ "F2": 0x3c, /* html:F2 (F2) -> linux:60 (KEY_F2) -> atset1:60 */
+ "F20": 0x5a, /* html:F20 (F20) -> linux:190 (KEY_F20) -> atset1:90 */
+ "F21": 0x74, /* html:F21 (F21) -> linux:191 (KEY_F21) -> atset1:116 */
+ "F22": 0xe079, /* html:F22 (F22) -> linux:192 (KEY_F22) -> atset1:57465 */
+ "F23": 0x6d, /* html:F23 (F23) -> linux:193 (KEY_F23) -> atset1:109 */
+ "F24": 0x6f, /* html:F24 (F24) -> linux:194 (KEY_F24) -> atset1:111 */
+ "F3": 0x3d, /* html:F3 (F3) -> linux:61 (KEY_F3) -> atset1:61 */
+ "F4": 0x3e, /* html:F4 (F4) -> linux:62 (KEY_F4) -> atset1:62 */
+ "F5": 0x3f, /* html:F5 (F5) -> linux:63 (KEY_F5) -> atset1:63 */
+ "F6": 0x40, /* html:F6 (F6) -> linux:64 (KEY_F6) -> atset1:64 */
+ "F7": 0x41, /* html:F7 (F7) -> linux:65 (KEY_F7) -> atset1:65 */
+ "F8": 0x42, /* html:F8 (F8) -> linux:66 (KEY_F8) -> atset1:66 */
+ "F9": 0x43, /* html:F9 (F9) -> linux:67 (KEY_F9) -> atset1:67 */
+ "Find": 0xe041, /* html:Find (Find) -> linux:136 (KEY_FIND) -> atset1:57409 */
+ "Help": 0xe075, /* html:Help (Help) -> linux:138 (KEY_HELP) -> atset1:57461 */
+ "Hiragana": 0x77, /* html:Hiragana (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
+ "Home": 0xe047, /* html:Home (Home) -> linux:102 (KEY_HOME) -> atset1:57415 */
+ "Insert": 0xe052, /* html:Insert (Insert) -> linux:110 (KEY_INSERT) -> atset1:57426 */
+ "IntlBackslash": 0x56, /* html:IntlBackslash (IntlBackslash) -> linux:86 (KEY_102ND) -> atset1:86 */
+ "IntlRo": 0x73, /* html:IntlRo (IntlRo) -> linux:89 (KEY_RO) -> atset1:115 */
+ "IntlYen": 0x7d, /* html:IntlYen (IntlYen) -> linux:124 (KEY_YEN) -> atset1:125 */
+ "KanaMode": 0x70, /* html:KanaMode (KanaMode) -> linux:93 (KEY_KATAKANAHIRAGANA) -> atset1:112 */
+ "Katakana": 0x78, /* html:Katakana (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
+ "KeyA": 0x1e, /* html:KeyA (KeyA) -> linux:30 (KEY_A) -> atset1:30 */
+ "KeyB": 0x30, /* html:KeyB (KeyB) -> linux:48 (KEY_B) -> atset1:48 */
+ "KeyC": 0x2e, /* html:KeyC (KeyC) -> linux:46 (KEY_C) -> atset1:46 */
+ "KeyD": 0x20, /* html:KeyD (KeyD) -> linux:32 (KEY_D) -> atset1:32 */
+ "KeyE": 0x12, /* html:KeyE (KeyE) -> linux:18 (KEY_E) -> atset1:18 */
+ "KeyF": 0x21, /* html:KeyF (KeyF) -> linux:33 (KEY_F) -> atset1:33 */
+ "KeyG": 0x22, /* html:KeyG (KeyG) -> linux:34 (KEY_G) -> atset1:34 */
+ "KeyH": 0x23, /* html:KeyH (KeyH) -> linux:35 (KEY_H) -> atset1:35 */
+ "KeyI": 0x17, /* html:KeyI (KeyI) -> linux:23 (KEY_I) -> atset1:23 */
+ "KeyJ": 0x24, /* html:KeyJ (KeyJ) -> linux:36 (KEY_J) -> atset1:36 */
+ "KeyK": 0x25, /* html:KeyK (KeyK) -> linux:37 (KEY_K) -> atset1:37 */
+ "KeyL": 0x26, /* html:KeyL (KeyL) -> linux:38 (KEY_L) -> atset1:38 */
+ "KeyM": 0x32, /* html:KeyM (KeyM) -> linux:50 (KEY_M) -> atset1:50 */
+ "KeyN": 0x31, /* html:KeyN (KeyN) -> linux:49 (KEY_N) -> atset1:49 */
+ "KeyO": 0x18, /* html:KeyO (KeyO) -> linux:24 (KEY_O) -> atset1:24 */
+ "KeyP": 0x19, /* html:KeyP (KeyP) -> linux:25 (KEY_P) -> atset1:25 */
+ "KeyQ": 0x10, /* html:KeyQ (KeyQ) -> linux:16 (KEY_Q) -> atset1:16 */
+ "KeyR": 0x13, /* html:KeyR (KeyR) -> linux:19 (KEY_R) -> atset1:19 */
+ "KeyS": 0x1f, /* html:KeyS (KeyS) -> linux:31 (KEY_S) -> atset1:31 */
+ "KeyT": 0x14, /* html:KeyT (KeyT) -> linux:20 (KEY_T) -> atset1:20 */
+ "KeyU": 0x16, /* html:KeyU (KeyU) -> linux:22 (KEY_U) -> atset1:22 */
+ "KeyV": 0x2f, /* html:KeyV (KeyV) -> linux:47 (KEY_V) -> atset1:47 */
+ "KeyW": 0x11, /* html:KeyW (KeyW) -> linux:17 (KEY_W) -> atset1:17 */
+ "KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
+ "KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
+ "KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
+ "Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
+ "Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
+ "Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
+ "LaunchApp1": 0xe06b, /* html:LaunchApp1 (LaunchApp1) -> linux:157 (KEY_COMPUTER) -> atset1:57451 */
+ "LaunchApp2": 0xe021, /* html:LaunchApp2 (LaunchApp2) -> linux:140 (KEY_CALC) -> atset1:57377 */
+ "LaunchMail": 0xe06c, /* html:LaunchMail (LaunchMail) -> linux:155 (KEY_MAIL) -> atset1:57452 */
+ "MediaPlayPause": 0xe022, /* html:MediaPlayPause (MediaPlayPause) -> linux:164 (KEY_PLAYPAUSE) -> atset1:57378 */
+ "MediaSelect": 0xe06d, /* html:MediaSelect (MediaSelect) -> linux:226 (KEY_MEDIA) -> atset1:57453 */
+ "MediaStop": 0xe024, /* html:MediaStop (MediaStop) -> linux:166 (KEY_STOPCD) -> atset1:57380 */
+ "MediaTrackNext": 0xe019, /* html:MediaTrackNext (MediaTrackNext) -> linux:163 (KEY_NEXTSONG) -> atset1:57369 */
+ "MediaTrackPrevious": 0xe010, /* html:MediaTrackPrevious (MediaTrackPrevious) -> linux:165 (KEY_PREVIOUSSONG) -> atset1:57360 */
+ "MetaLeft": 0xe05b, /* html:MetaLeft (MetaLeft) -> linux:125 (KEY_LEFTMETA) -> atset1:57435 */
+ "MetaRight": 0xe05c, /* html:MetaRight (MetaRight) -> linux:126 (KEY_RIGHTMETA) -> atset1:57436 */
+ "Minus": 0xc, /* html:Minus (Minus) -> linux:12 (KEY_MINUS) -> atset1:12 */
+ "NonConvert": 0x7b, /* html:NonConvert (NonConvert) -> linux:94 (KEY_MUHENKAN) -> atset1:123 */
+ "NumLock": 0x45, /* html:NumLock (NumLock) -> linux:69 (KEY_NUMLOCK) -> atset1:69 */
+ "Numpad0": 0x52, /* html:Numpad0 (Numpad0) -> linux:82 (KEY_KP0) -> atset1:82 */
+ "Numpad1": 0x4f, /* html:Numpad1 (Numpad1) -> linux:79 (KEY_KP1) -> atset1:79 */
+ "Numpad2": 0x50, /* html:Numpad2 (Numpad2) -> linux:80 (KEY_KP2) -> atset1:80 */
+ "Numpad3": 0x51, /* html:Numpad3 (Numpad3) -> linux:81 (KEY_KP3) -> atset1:81 */
+ "Numpad4": 0x4b, /* html:Numpad4 (Numpad4) -> linux:75 (KEY_KP4) -> atset1:75 */
+ "Numpad5": 0x4c, /* html:Numpad5 (Numpad5) -> linux:76 (KEY_KP5) -> atset1:76 */
+ "Numpad6": 0x4d, /* html:Numpad6 (Numpad6) -> linux:77 (KEY_KP6) -> atset1:77 */
+ "Numpad7": 0x47, /* html:Numpad7 (Numpad7) -> linux:71 (KEY_KP7) -> atset1:71 */
+ "Numpad8": 0x48, /* html:Numpad8 (Numpad8) -> linux:72 (KEY_KP8) -> atset1:72 */
+ "Numpad9": 0x49, /* html:Numpad9 (Numpad9) -> linux:73 (KEY_KP9) -> atset1:73 */
+ "NumpadAdd": 0x4e, /* html:NumpadAdd (NumpadAdd) -> linux:78 (KEY_KPPLUS) -> atset1:78 */
+ "NumpadComma": 0x7e, /* html:NumpadComma (NumpadComma) -> linux:121 (KEY_KPCOMMA) -> atset1:126 */
+ "NumpadDecimal": 0x53, /* html:NumpadDecimal (NumpadDecimal) -> linux:83 (KEY_KPDOT) -> atset1:83 */
+ "NumpadDivide": 0xe035, /* html:NumpadDivide (NumpadDivide) -> linux:98 (KEY_KPSLASH) -> atset1:57397 */
+ "NumpadEnter": 0xe01c, /* html:NumpadEnter (NumpadEnter) -> linux:96 (KEY_KPENTER) -> atset1:57372 */
+ "NumpadEqual": 0x59, /* html:NumpadEqual (NumpadEqual) -> linux:117 (KEY_KPEQUAL) -> atset1:89 */
+ "NumpadMultiply": 0x37, /* html:NumpadMultiply (NumpadMultiply) -> linux:55 (KEY_KPASTERISK) -> atset1:55 */
+ "NumpadParenLeft": 0xe076, /* html:NumpadParenLeft (NumpadParenLeft) -> linux:179 (KEY_KPLEFTPAREN) -> atset1:57462 */
+ "NumpadParenRight": 0xe07b, /* html:NumpadParenRight (NumpadParenRight) -> linux:180 (KEY_KPRIGHTPAREN) -> atset1:57467 */
+ "NumpadSubtract": 0x4a, /* html:NumpadSubtract (NumpadSubtract) -> linux:74 (KEY_KPMINUS) -> atset1:74 */
+ "Open": 0x64, /* html:Open (Open) -> linux:134 (KEY_OPEN) -> atset1:100 */
+ "PageDown": 0xe051, /* html:PageDown (PageDown) -> linux:109 (KEY_PAGEDOWN) -> atset1:57425 */
+ "PageUp": 0xe049, /* html:PageUp (PageUp) -> linux:104 (KEY_PAGEUP) -> atset1:57417 */
+ "Paste": 0x65, /* html:Paste (Paste) -> linux:135 (KEY_PASTE) -> atset1:101 */
+ "Pause": 0xe046, /* html:Pause (Pause) -> linux:119 (KEY_PAUSE) -> atset1:57414 */
+ "Period": 0x34, /* html:Period (Period) -> linux:52 (KEY_DOT) -> atset1:52 */
+ "Power": 0xe05e, /* html:Power (Power) -> linux:116 (KEY_POWER) -> atset1:57438 */
+ "PrintScreen": 0x54, /* html:PrintScreen (PrintScreen) -> linux:99 (KEY_SYSRQ) -> atset1:84 */
+ "Props": 0xe006, /* html:Props (Props) -> linux:130 (KEY_PROPS) -> atset1:57350 */
+ "Quote": 0x28, /* html:Quote (Quote) -> linux:40 (KEY_APOSTROPHE) -> atset1:40 */
+ "ScrollLock": 0x46, /* html:ScrollLock (ScrollLock) -> linux:70 (KEY_SCROLLLOCK) -> atset1:70 */
+ "Semicolon": 0x27, /* html:Semicolon (Semicolon) -> linux:39 (KEY_SEMICOLON) -> atset1:39 */
+ "ShiftLeft": 0x2a, /* html:ShiftLeft (ShiftLeft) -> linux:42 (KEY_LEFTSHIFT) -> atset1:42 */
+ "ShiftRight": 0x36, /* html:ShiftRight (ShiftRight) -> linux:54 (KEY_RIGHTSHIFT) -> atset1:54 */
+ "Slash": 0x35, /* html:Slash (Slash) -> linux:53 (KEY_SLASH) -> atset1:53 */
+ "Sleep": 0xe05f, /* html:Sleep (Sleep) -> linux:142 (KEY_SLEEP) -> atset1:57439 */
+ "Space": 0x39, /* html:Space (Space) -> linux:57 (KEY_SPACE) -> atset1:57 */
+ "Suspend": 0xe025, /* html:Suspend (Suspend) -> linux:205 (KEY_SUSPEND) -> atset1:57381 */
+ "Tab": 0xf, /* html:Tab (Tab) -> linux:15 (KEY_TAB) -> atset1:15 */
+ "Undo": 0xe007, /* html:Undo (Undo) -> linux:131 (KEY_UNDO) -> atset1:57351 */
+ "WakeUp": 0xe063, /* html:WakeUp (WakeUp) -> linux:143 (KEY_WAKEUP) -> atset1:57443 */
+};
diff --git a/systemvm/agent/noVNC/core/rfb.js b/systemvm/agent/noVNC/core/rfb.js
new file mode 100644
index 0000000..e40df66
--- /dev/null
+++ b/systemvm/agent/noVNC/core/rfb.js
@@ -0,0 +1,2060 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import * as Log from './util/logging.js';
+import { decodeUTF8 } from './util/strings.js';
+import { dragThreshold } from './util/browser.js';
+import EventTargetMixin from './util/eventtarget.js';
+import Display from "./display.js";
+import Keyboard from "./input/keyboard.js";
+import Mouse from "./input/mouse.js";
+import Cursor from "./util/cursor.js";
+import Websock from "./websock.js";
+import DES from "./des.js";
+import KeyTable from "./input/keysym.js";
+import XtScancode from "./input/xtscancodes.js";
+import { encodings } from "./encodings.js";
+import "./util/polyfill.js";
+
+import RawDecoder from "./decoders/raw.js";
+import CopyRectDecoder from "./decoders/copyrect.js";
+import RREDecoder from "./decoders/rre.js";
+import HextileDecoder from "./decoders/hextile.js";
+import TightDecoder from "./decoders/tight.js";
+import TightPNGDecoder from "./decoders/tightpng.js";
+
+// How many seconds to wait for a disconnect to finish
+const DISCONNECT_TIMEOUT = 3;
+const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
+
+export default class RFB extends EventTargetMixin {
+ constructor(target, url, options) {
+ if (!target) {
+ throw new Error("Must specify target");
+ }
+ if (!url) {
+ throw new Error("Must specify URL");
+ }
+
+ super();
+
+ this._target = target;
+ this._url = url;
+
+ // Connection details
+ options = options || {};
+ this._rfb_credentials = options.credentials || {};
+ this._shared = false;
+ this._repeaterID = options.repeaterID || '';
+ this._showDotCursor = options.showDotCursor || false;
+
+ // Internal state
+ this._rfb_connection_state = '';
+ this._rfb_init_state = '';
+ this._rfb_auth_scheme = -1;
+ this._rfb_clean_disconnect = true;
+
+ // Server capabilities
+ this._rfb_version = 0;
+ this._rfb_max_version = 3.8;
+ this._rfb_tightvnc = false;
+ this._rfb_xvp_ver = 0;
+
+ this._fb_width = 0;
+ this._fb_height = 0;
+
+ this._fb_name = "";
+
+ this._capabilities = { power: false };
+
+ this._supportsFence = false;
+
+ this._supportsContinuousUpdates = false;
+ this._enabledContinuousUpdates = false;
+
+ this._supportsSetDesktopSize = false;
+ this._screen_id = 0;
+ this._screen_flags = 0;
+
+ this._qemuExtKeyEventSupported = false;
+
+ // Internal objects
+ this._sock = null; // Websock object
+ this._display = null; // Display object
+ this._flushing = false; // Display flushing state
+ this._keyboard = null; // Keyboard input handler object
+ this._mouse = null; // Mouse input handler object
+
+ // Timers
+ this._disconnTimer = null; // disconnection timer
+ this._resizeTimeout = null; // resize rate limiting
+
+ // Decoder states
+ this._decoders = {};
+
+ this._FBU = {
+ rects: 0,
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ encoding: null,
+ };
+
+ // Mouse state
+ this._mouse_buttonMask = 0;
+ this._mouse_arr = [];
+ this._viewportDragging = false;
+ this._viewportDragPos = {};
+ this._viewportHasMoved = false;
+
+ // Bound event handlers
+ this._eventHandlers = {
+ focusCanvas: this._focusCanvas.bind(this),
+ windowResize: this._windowResize.bind(this),
+ };
+
+ // main setup
+ Log.Debug(">> RFB.constructor");
+
+ // Create DOM elements
+ this._screen = document.createElement('div');
+ this._screen.style.display = 'flex';
+ this._screen.style.width = '100%';
+ this._screen.style.height = '100%';
+ this._screen.style.overflow = 'auto';
+ this._screen.style.background = DEFAULT_BACKGROUND;
+ this._canvas = document.createElement('canvas');
+ this._canvas.style.margin = 'auto';
+ // Some browsers add an outline on focus
+ this._canvas.style.outline = 'none';
+ // IE miscalculates width without this :(
+ this._canvas.style.flexShrink = '0';
+ this._canvas.width = 0;
+ this._canvas.height = 0;
+ this._canvas.tabIndex = -1;
+ this._screen.appendChild(this._canvas);
+
+ // Cursor
+ this._cursor = new Cursor();
+
+ // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
+ // it. Result: no cursor at all until a window border or an edit field
+ // is hit blindly. But there are also VNC servers that draw the cursor
+ // in the framebuffer and don't send the empty local cursor. There is
+ // no way to satisfy both sides.
+ //
+ // The spec is unclear on this "initial cursor" issue. Many other
+ // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
+ // initial cursor instead.
+ this._cursorImage = RFB.cursors.none;
+
+ // populate decoder array with objects
+ this._decoders[encodings.encodingRaw] = new RawDecoder();
+ this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
+ this._decoders[encodings.encodingRRE] = new RREDecoder();
+ this._decoders[encodings.encodingHextile] = new HextileDecoder();
+ this._decoders[encodings.encodingTight] = new TightDecoder();
+ this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
+
+ // NB: nothing that needs explicit teardown should be done
+ // before this point, since this can throw an exception
+ try {
+ this._display = new Display(this._canvas);
+ } catch (exc) {
+ Log.Error("Display exception: " + exc);
+ throw exc;
+ }
+ this._display.onflush = this._onFlush.bind(this);
+ this._display.clear();
+
+ this._keyboard = new Keyboard(this._canvas);
+ this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
+
+ this._mouse = new Mouse(this._canvas);
+ this._mouse.onmousebutton = this._handleMouseButton.bind(this);
+ this._mouse.onmousemove = this._handleMouseMove.bind(this);
+
+ this._sock = new Websock();
+ this._sock.on('message', () => {
+ this._handle_message();
+ });
+ this._sock.on('open', () => {
+ if ((this._rfb_connection_state === 'connecting') &&
+ (this._rfb_init_state === '')) {
+ this._rfb_init_state = 'ProtocolVersion';
+ Log.Debug("Starting VNC handshake");
+ } else {
+ this._fail("Unexpected server connection while " +
+ this._rfb_connection_state);
+ }
+ });
+ this._sock.on('close', (e) => {
+ Log.Debug("WebSocket on-close event");
+ let msg = "";
+ if (e.code) {
+ msg = "(code: " + e.code;
+ if (e.reason) {
+ msg += ", reason: " + e.reason;
+ }
+ msg += ")";
+ }
+ switch (this._rfb_connection_state) {
+ case 'connecting':
+ this._fail("Connection closed " + msg);
+ break;
+ case 'connected':
+ // Handle disconnects that were initiated server-side
+ this._updateConnectionState('disconnecting');
+ this._updateConnectionState('disconnected');
+ break;
+ case 'disconnecting':
+ // Normal disconnection path
+ this._updateConnectionState('disconnected');
+ break;
+ case 'disconnected':
+ this._fail("Unexpected server disconnect " +
+ "when already disconnected " + msg);
+ break;
+ default:
+ this._fail("Unexpected server disconnect before connecting " +
+ msg);
+ break;
+ }
+ this._sock.off('close');
+ });
+ this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
+
+ // Slight delay of the actual connection so that the caller has
+ // time to set up callbacks
+ setTimeout(this._updateConnectionState.bind(this, 'connecting'));
+
+ Log.Debug("<< RFB.constructor");
+
+ // ===== PROPERTIES =====
+
+ this.dragViewport = false;
+ this.focusOnClick = true;
+
+ this._viewOnly = false;
+ this._clipViewport = false;
+ this._scaleViewport = false;
+ this._resizeSession = false;
+ }
+
+ // ===== PROPERTIES =====
+
+ get viewOnly() { return this._viewOnly; }
+ set viewOnly(viewOnly) {
+ this._viewOnly = viewOnly;
+
+ if (this._rfb_connection_state === "connecting" ||
+ this._rfb_connection_state === "connected") {
+ if (viewOnly) {
+ this._keyboard.ungrab();
+ this._mouse.ungrab();
+ } else {
+ this._keyboard.grab();
+ this._mouse.grab();
+ }
+ }
+ }
+
+ get capabilities() { return this._capabilities; }
+
+ get touchButton() { return this._mouse.touchButton; }
+ set touchButton(button) { this._mouse.touchButton = button; }
+
+ get clipViewport() { return this._clipViewport; }
+ set clipViewport(viewport) {
+ this._clipViewport = viewport;
+ this._updateClip();
+ }
+
+ get scaleViewport() { return this._scaleViewport; }
+ set scaleViewport(scale) {
+ this._scaleViewport = scale;
+ // Scaling trumps clipping, so we may need to adjust
+ // clipping when enabling or disabling scaling
+ if (scale && this._clipViewport) {
+ this._updateClip();
+ }
+ this._updateScale();
+ if (!scale && this._clipViewport) {
+ this._updateClip();
+ }
+ }
+
+ get resizeSession() { return this._resizeSession; }
+ set resizeSession(resize) {
+ this._resizeSession = resize;
+ if (resize) {
+ this._requestRemoteResize();
+ }
+ }
+
+ get showDotCursor() { return this._showDotCursor; }
+ set showDotCursor(show) {
+ this._showDotCursor = show;
+ this._refreshCursor();
+ }
+
+ get background() { return this._screen.style.background; }
+ set background(cssValue) { this._screen.style.background = cssValue; }
+
+ // ===== PUBLIC METHODS =====
+
+ disconnect() {
+ this._updateConnectionState('disconnecting');
+ this._sock.off('error');
+ this._sock.off('message');
+ this._sock.off('open');
+ }
+
+ sendCredentials(creds) {
+ this._rfb_credentials = creds;
+ setTimeout(this._init_msg.bind(this), 0);
+ }
+
+ sendCtrlAltDel() {
+ if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
+ Log.Info("Sending Ctrl-Alt-Del");
+
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+ this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+ this.sendKey(KeyTable.XK_Delete, "Delete", true);
+ this.sendKey(KeyTable.XK_Delete, "Delete", false);
+ this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+ }
+
+ sendCtrlEsc() {
+ if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
+ Log.Info("Sending Ctrl-Esc");
+
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+ this.sendKey(KeyTable.XK_Escape, "Escape", true);
+ this.sendKey(KeyTable.XK_Escape, "Escape", false);
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+ }
+
+ machineShutdown() {
+ this._xvpOp(1, 2);
+ }
+
+ machineReboot() {
+ this._xvpOp(1, 3);
+ }
+
+ machineReset() {
+ this._xvpOp(1, 4);
+ }
+
+ // Send a key press. If 'down' is not specified then send a down key
+ // followed by an up key.
+ sendKey(keysym, code, down) {
+ if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
+
+ if (down === undefined) {
+ this.sendKey(keysym, code, true);
+ this.sendKey(keysym, code, false);
+ return;
+ }
+
+ const scancode = XtScancode[code];
+
+ if (this._qemuExtKeyEventSupported && scancode) {
+ // 0 is NoSymbol
+ keysym = keysym || 0;
+
+ Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
+
+ RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
+ } else {
+ if (!keysym) {
+ return;
+ }
+ Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
+ RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
+ }
+ }
+
+ focus() {
+ this._canvas.focus();
+ }
+
+ blur() {
+ this._canvas.blur();
+ }
+
+ clipboardPasteFrom(text) {
+ if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
+ RFB.messages.clientCutText(this._sock, text);
+ }
+
+ // ===== PRIVATE METHODS =====
+
+ _connect() {
+ Log.Debug(">> RFB.connect");
+
+ Log.Info("connecting to " + this._url);
+
+ try {
+ // WebSocket.onopen transitions to the RFB init states
+ this._sock.open(this._url, ['binary']);
+ } catch (e) {
+ if (e.name === 'SyntaxError') {
+ this._fail("Invalid host or port (" + e + ")");
+ } else {
+ this._fail("Error when opening socket (" + e + ")");
+ }
+ }
+
+ // Make our elements part of the page
+ this._target.appendChild(this._screen);
+
+ this._cursor.attach(this._canvas);
+ this._refreshCursor();
+
+ // Monitor size changes of the screen
+ // FIXME: Use ResizeObserver, or hidden overflow
+ window.addEventListener('resize', this._eventHandlers.windowResize);
+
+ // Always grab focus on some kind of click event
+ this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
+ this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
+
+ Log.Debug("<< RFB.connect");
+ }
+
+ _disconnect() {
+ Log.Debug(">> RFB.disconnect");
+ this._cursor.detach();
+ this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
+ this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
+ window.removeEventListener('resize', this._eventHandlers.windowResize);
+ this._keyboard.ungrab();
+ this._mouse.ungrab();
+ this._sock.close();
+ try {
+ this._target.removeChild(this._screen);
+ } catch (e) {
+ if (e.name === 'NotFoundError') {
+ // Some cases where the initial connection fails
+ // can disconnect before the _screen is created
+ } else {
+ throw e;
+ }
+ }
+ clearTimeout(this._resizeTimeout);
+ Log.Debug("<< RFB.disconnect");
+ }
+
+ _focusCanvas(event) {
+ // Respect earlier handlers' request to not do side-effects
+ if (event.defaultPrevented) {
+ return;
+ }
+
+ if (!this.focusOnClick) {
+ return;
+ }
+
+ this.focus();
+ }
+
+ _windowResize(event) {
+ // If the window resized then our screen element might have
+ // as well. Update the viewport dimensions.
+ window.requestAnimationFrame(() => {
+ this._updateClip();
+ this._updateScale();
+ });
+
+ if (this._resizeSession) {
+ // Request changing the resolution of the remote display to
+ // the size of the local browser viewport.
+
+ // In order to not send multiple requests before the browser-resize
+ // is finished we wait 0.5 seconds before sending the request.
+ clearTimeout(this._resizeTimeout);
+ this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
+ }
+ }
+
+ // Update state of clipping in Display object, and make sure the
+ // configured viewport matches the current screen size
+ _updateClip() {
+ const cur_clip = this._display.clipViewport;
+ let new_clip = this._clipViewport;
+
+ if (this._scaleViewport) {
+ // Disable viewport clipping if we are scaling
+ new_clip = false;
+ }
+
+ if (cur_clip !== new_clip) {
+ this._display.clipViewport = new_clip;
+ }
+
+ if (new_clip) {
+ // When clipping is enabled, the screen is limited to
+ // the size of the container.
+ const size = this._screenSize();
+ this._display.viewportChangeSize(size.w, size.h);
+ this._fixScrollbars();
+ }
+ }
+
+ _updateScale() {
+ if (!this._scaleViewport) {
+ this._display.scale = 1.0;
+ } else {
+ const size = this._screenSize();
+ this._display.autoscale(size.w, size.h);
+ }
+ this._fixScrollbars();
+ }
+
+ // Requests a change of remote desktop size. This message is an extension
+ // and may only be sent if we have received an ExtendedDesktopSize message
+ _requestRemoteResize() {
+ clearTimeout(this._resizeTimeout);
+ this._resizeTimeout = null;
+
+ if (!this._resizeSession || this._viewOnly ||
+ !this._supportsSetDesktopSize) {
+ return;
+ }
+
+ const size = this._screenSize();
+ RFB.messages.setDesktopSize(this._sock,
+ Math.floor(size.w), Math.floor(size.h),
+ this._screen_id, this._screen_flags);
+
+ Log.Debug('Requested new desktop size: ' +
+ size.w + 'x' + size.h);
+ }
+
+ // Gets the the size of the available screen
+ _screenSize() {
+ let r = this._screen.getBoundingClientRect();
+ return { w: r.width, h: r.height };
+ }
+
+ _fixScrollbars() {
+ // This is a hack because Chrome screws up the calculation
+ // for when scrollbars are needed. So to fix it we temporarily
+ // toggle them off and on.
+ const orig = this._screen.style.overflow;
+ this._screen.style.overflow = 'hidden';
+ // Force Chrome to recalculate the layout by asking for
+ // an element's dimensions
+ this._screen.getBoundingClientRect();
+ this._screen.style.overflow = orig;
+ }
+
+ /*
+ * Connection states:
+ * connecting
+ * connected
+ * disconnecting
+ * disconnected - permanent state
+ */
+ _updateConnectionState(state) {
+ const oldstate = this._rfb_connection_state;
+
+ if (state === oldstate) {
+ Log.Debug("Already in state '" + state + "', ignoring");
+ return;
+ }
+
+ // The 'disconnected' state is permanent for each RFB object
+ if (oldstate === 'disconnected') {
+ Log.Error("Tried changing state of a disconnected RFB object");
+ return;
+ }
+
+ // Ensure proper transitions before doing anything
+ switch (state) {
+ case 'connected':
+ if (oldstate !== 'connecting') {
+ Log.Error("Bad transition to connected state, " +
+ "previous connection state: " + oldstate);
+ return;
+ }
+ break;
+
+ case 'disconnected':
+ if (oldstate !== 'disconnecting') {
+ Log.Error("Bad transition to disconnected state, " +
+ "previous connection state: " + oldstate);
+ return;
+ }
+ break;
+
+ case 'connecting':
+ if (oldstate !== '') {
+ Log.Error("Bad transition to connecting state, " +
+ "previous connection state: " + oldstate);
+ return;
+ }
+ break;
+
+ case 'disconnecting':
+ if (oldstate !== 'connected' && oldstate !== 'connecting') {
+ Log.Error("Bad transition to disconnecting state, " +
+ "previous connection state: " + oldstate);
+ return;
+ }
+ break;
+
+ default:
+ Log.Error("Unknown connection state: " + state);
+ return;
+ }
+
+ // State change actions
+
+ this._rfb_connection_state = state;
+
+ Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
+
+ if (this._disconnTimer && state !== 'disconnecting') {
+ Log.Debug("Clearing disconnect timer");
+ clearTimeout(this._disconnTimer);
+ this._disconnTimer = null;
+
+ // make sure we don't get a double event
+ this._sock.off('close');
+ }
+
+ switch (state) {
+ case 'connecting':
+ this._connect();
+ break;
+
+ case 'connected':
+ this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
+ break;
+
+ case 'disconnecting':
+ this._disconnect();
+
+ this._disconnTimer = setTimeout(() => {
+ Log.Error("Disconnection timed out.");
+ this._updateConnectionState('disconnected');
+ }, DISCONNECT_TIMEOUT * 1000);
+ break;
+
+ case 'disconnected':
+ this.dispatchEvent(new CustomEvent(
+ "disconnect", { detail:
+ { clean: this._rfb_clean_disconnect } }));
+ break;
+ }
+ }
+
+ /* Print errors and disconnect
+ *
+ * The parameter 'details' is used for information that
+ * should be logged but not sent to the user interface.
+ */
+ _fail(details) {
+ switch (this._rfb_connection_state) {
+ case 'disconnecting':
+ Log.Error("Failed when disconnecting: " + details);
+ break;
+ case 'connected':
+ Log.Error("Failed while connected: " + details);
+ break;
+ case 'connecting':
+ Log.Error("Failed when connecting: " + details);
+ break;
+ default:
+ Log.Error("RFB failure: " + details);
+ break;
+ }
+ this._rfb_clean_disconnect = false; //This is sent to the UI
+
+ // Transition to disconnected without waiting for socket to close
+ this._updateConnectionState('disconnecting');
+ this._updateConnectionState('disconnected');
+
+ return false;
+ }
+
+ _setCapability(cap, val) {
+ this._capabilities[cap] = val;
+ this.dispatchEvent(new CustomEvent("capabilities",
+ { detail: { capabilities: this._capabilities } }));
+ }
+
+ _handle_message() {
+ if (this._sock.rQlen === 0) {
+ Log.Warn("handle_message called on an empty receive queue");
+ return;
+ }
+
+ switch (this._rfb_connection_state) {
+ case 'disconnected':
+ Log.Error("Got data while disconnected");
+ break;
+ case 'connected':
+ while (true) {
+ if (this._flushing) {
+ break;
+ }
+ if (!this._normal_msg()) {
+ break;
+ }
+ if (this._sock.rQlen === 0) {
+ break;
+ }
+ }
+ break;
+ default:
+ this._init_msg();
+ break;
+ }
+ }
+
+ _handleKeyEvent(keysym, code, down) {
+ this.sendKey(keysym, code, down);
+ }
+
+ _handleMouseButton(x, y, down, bmask) {
+ if (down) {
+ this._mouse_buttonMask |= bmask;
+ } else {
+ this._mouse_buttonMask &= ~bmask;
+ }
+
+ if (this.dragViewport) {
+ if (down && !this._viewportDragging) {
+ this._viewportDragging = true;
+ this._viewportDragPos = {'x': x, 'y': y};
+ this._viewportHasMoved = false;
+
+ // Skip sending mouse events
+ return;
+ } else {
+ this._viewportDragging = false;
+
+ // If we actually performed a drag then we are done
+ // here and should not send any mouse events
+ if (this._viewportHasMoved) {
+ return;
+ }
+
+ // Otherwise we treat this as a mouse click event.
+ // Send the button down event here, as the button up
+ // event is sent at the end of this function.
+ RFB.messages.pointerEvent(this._sock,
+ this._display.absX(x),
+ this._display.absY(y),
+ bmask);
+ }
+ }
+
+ if (this._viewOnly) { return; } // View only, skip mouse events
+
+ if (this._rfb_connection_state !== 'connected') { return; }
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+ }
+
+ _handleMouseMove(x, y) {
+ if (this._viewportDragging) {
+ const deltaX = this._viewportDragPos.x - x;
+ const deltaY = this._viewportDragPos.y - y;
+
+ if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
+ Math.abs(deltaY) > dragThreshold)) {
+ this._viewportHasMoved = true;
+
+ this._viewportDragPos = {'x': x, 'y': y};
+ this._display.viewportChangePos(deltaX, deltaY);
+ }
+
+ // Skip sending mouse events
+ return;
+ }
+
+ if (this._viewOnly) { return; } // View only, skip mouse events
+
+ if (this._rfb_connection_state !== 'connected') { return; }
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+ }
+
+ // Message Handlers
+
+ _negotiate_protocol_version() {
+ if (this._sock.rQwait("version", 12)) {
+ return false;
+ }
+
+ const sversion = this._sock.rQshiftStr(12).substr(4, 7);
+ Log.Info("Server ProtocolVersion: " + sversion);
+ let is_repeater = 0;
+ switch (sversion) {
+ case "000.000": // UltraVNC repeater
+ is_repeater = 1;
+ break;
+ case "003.003":
+ case "003.006": // UltraVNC
+ case "003.889": // Apple Remote Desktop
+ this._rfb_version = 3.3;
+ break;
+ case "003.007":
+ this._rfb_version = 3.7;
+ break;
+ case "003.008":
+ case "004.000": // Intel AMT KVM
+ case "004.001": // RealVNC 4.6
+ case "005.000": // RealVNC 5.3
+ this._rfb_version = 3.8;
+ break;
+ default:
+ return this._fail("Invalid server version " + sversion);
+ }
+
+ if (is_repeater) {
+ let repeaterID = "ID:" + this._repeaterID;
+ while (repeaterID.length < 250) {
+ repeaterID += "\0";
+ }
+ this._sock.send_string(repeaterID);
+ return true;
+ }
+
+ if (this._rfb_version > this._rfb_max_version) {
+ this._rfb_version = this._rfb_max_version;
+ }
+
+ const cversion = "00" + parseInt(this._rfb_version, 10) +
+ ".00" + ((this._rfb_version * 10) % 10);
+ this._sock.send_string("RFB " + cversion + "\n");
+ Log.Debug('Sent ProtocolVersion: ' + cversion);
+
+ this._rfb_init_state = 'Security';
+ }
+
+ _negotiate_security() {
+ // Polyfill since IE and PhantomJS doesn't have
+ // TypedArray.includes()
+ function includes(item, array) {
+ for (let i = 0; i < array.length; i++) {
+ if (array[i] === item) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (this._rfb_version >= 3.7) {
+ // Server sends supported list, client decides
+ const num_types = this._sock.rQshift8();
+ if (this._sock.rQwait("security type", num_types, 1)) { return false; }
+
+ if (num_types === 0) {
+ this._rfb_init_state = "SecurityReason";
+ this._security_context = "no security types";
+ this._security_status = 1;
+ return this._init_msg();
+ }
+
+ const types = this._sock.rQshiftBytes(num_types);
+ Log.Debug("Server security types: " + types);
+
+ // Look for each auth in preferred order
+ if (includes(1, types)) {
+ this._rfb_auth_scheme = 1; // None
+ } else if (includes(22, types)) {
+ this._rfb_auth_scheme = 22; // XVP
+ } else if (includes(16, types)) {
+ this._rfb_auth_scheme = 16; // Tight
+ } else if (includes(2, types)) {
+ this._rfb_auth_scheme = 2; // VNC Auth
+ } else {
+ return this._fail("Unsupported security types (types: " + types + ")");
+ }
+
+ this._sock.send([this._rfb_auth_scheme]);
+ } else {
+ // Server decides
+ if (this._sock.rQwait("security scheme", 4)) { return false; }
+ this._rfb_auth_scheme = this._sock.rQshift32();
+
+ if (this._rfb_auth_scheme == 0) {
+ this._rfb_init_state = "SecurityReason";
+ this._security_context = "authentication scheme";
+ this._security_status = 1;
+ return this._init_msg();
+ }
+ }
+
+ this._rfb_init_state = 'Authentication';
+ Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
+
+ return this._init_msg(); // jump to authentication
+ }
+
+ _handle_security_reason() {
+ if (this._sock.rQwait("reason length", 4)) {
+ return false;
+ }
+ const strlen = this._sock.rQshift32();
+ let reason = "";
+
+ if (strlen > 0) {
+ if (this._sock.rQwait("reason", strlen, 4)) { return false; }
+ reason = this._sock.rQshiftStr(strlen);
+ }
+
+ if (reason !== "") {
+ this.dispatchEvent(new CustomEvent(
+ "securityfailure",
+ { detail: { status: this._security_status,
+ reason: reason } }));
+
+ return this._fail("Security negotiation failed on " +
+ this._security_context +
+ " (reason: " + reason + ")");
+ } else {
+ this.dispatchEvent(new CustomEvent(
+ "securityfailure",
+ { detail: { status: this._security_status } }));
+
+ return this._fail("Security negotiation failed on " +
+ this._security_context);
+ }
+ }
+
+ // authentication
+ _negotiate_xvp_auth() {
+ if (!this._rfb_credentials.username ||
+ !this._rfb_credentials.password ||
+ !this._rfb_credentials.target) {
+ this.dispatchEvent(new CustomEvent(
+ "credentialsrequired",
+ { detail: { types: ["username", "password", "target"] } }));
+ return false;
+ }
+
+ const xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
+ String.fromCharCode(this._rfb_credentials.target.length) +
+ this._rfb_credentials.username +
+ this._rfb_credentials.target;
+ this._sock.send_string(xvp_auth_str);
+ this._rfb_auth_scheme = 2;
+ return this._negotiate_authentication();
+ }
+
+ _negotiate_std_vnc_auth() {
+ if (this._sock.rQwait("auth challenge", 16)) { return false; }
+
+ if (!this._rfb_credentials.password) {
+ this.dispatchEvent(new CustomEvent(
+ "credentialsrequired",
+ { detail: { types: ["password"] } }));
+ return false;
+ }
+
+ // TODO(directxman12): make genDES not require an Array
+ const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
+ const response = RFB.genDES(this._rfb_credentials.password, challenge);
+ this._sock.send(response);
+ this._rfb_init_state = "SecurityResult";
+ return true;
+ }
+
+ _negotiate_tight_tunnels(numTunnels) {
+ const clientSupportedTunnelTypes = {
+ 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
+ };
+ const serverSupportedTunnelTypes = {};
+ // receive tunnel capabilities
+ for (let i = 0; i < numTunnels; i++) {
+ const cap_code = this._sock.rQshift32();
+ const cap_vendor = this._sock.rQshiftStr(4);
+ const cap_signature = this._sock.rQshiftStr(8);
+ serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
+ }
+
+ Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
+
+ // Siemens touch panels have a VNC server that supports NOTUNNEL,
+ // but forgets to advertise it. Try to detect such servers by
+ // looking for their custom tunnel type.
+ if (serverSupportedTunnelTypes[1] &&
+ (serverSupportedTunnelTypes[1].vendor === "SICR") &&
+ (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
+ Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
+ serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
+ }
+
+ // choose the notunnel type
+ if (serverSupportedTunnelTypes[0]) {
+ if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
+ serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
+ return this._fail("Client's tunnel type had the incorrect " +
+ "vendor or signature");
+ }
+ Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
+ this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
+ return false; // wait until we receive the sub auth count to continue
+ } else {
+ return this._fail("Server wanted tunnels, but doesn't support " +
+ "the notunnel type");
+ }
+ }
+
+ _negotiate_tight_auth() {
+ if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
+ if (this._sock.rQwait("num tunnels", 4)) { return false; }
+ const numTunnels = this._sock.rQshift32();
+ if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
+
+ this._rfb_tightvnc = true;
+
+ if (numTunnels > 0) {
+ this._negotiate_tight_tunnels(numTunnels);
+ return false; // wait until we receive the sub auth to continue
+ }
+ }
+
+ // second pass, do the sub-auth negotiation
+ if (this._sock.rQwait("sub auth count", 4)) { return false; }
+ const subAuthCount = this._sock.rQshift32();
+ if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
+ this._rfb_init_state = 'SecurityResult';
+ return true;
+ }
+
+ if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
+
+ const clientSupportedTypes = {
+ 'STDVNOAUTH__': 1,
+ 'STDVVNCAUTH_': 2
+ };
+
+ const serverSupportedTypes = [];
+
+ for (let i = 0; i < subAuthCount; i++) {
+ this._sock.rQshift32(); // capNum
+ const capabilities = this._sock.rQshiftStr(12);
+ serverSupportedTypes.push(capabilities);
+ }
+
+ Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
+
+ for (let authType in clientSupportedTypes) {
+ if (serverSupportedTypes.indexOf(authType) != -1) {
+ this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
+ Log.Debug("Selected authentication type: " + authType);
+
+ switch (authType) {
+ case 'STDVNOAUTH__': // no auth
+ this._rfb_init_state = 'SecurityResult';
+ return true;
+ case 'STDVVNCAUTH_': // VNC auth
+ this._rfb_auth_scheme = 2;
+ return this._init_msg();
+ default:
+ return this._fail("Unsupported tiny auth scheme " +
+ "(scheme: " + authType + ")");
+ }
+ }
+ }
+
+ return this._fail("No supported sub-auth types!");
+ }
+
+ _negotiate_authentication() {
+ switch (this._rfb_auth_scheme) {
+ case 1: // no auth
+ if (this._rfb_version >= 3.8) {
+ this._rfb_init_state = 'SecurityResult';
+ return true;
+ }
+ this._rfb_init_state = 'ClientInitialisation';
+ return this._init_msg();
+
+ case 22: // XVP auth
+ return this._negotiate_xvp_auth();
+
+ case 2: // VNC authentication
+ return this._negotiate_std_vnc_auth();
+
+ case 16: // TightVNC Security Type
+ return this._negotiate_tight_auth();
+
+ default:
+ return this._fail("Unsupported auth scheme (scheme: " +
+ this._rfb_auth_scheme + ")");
+ }
+ }
+
+ _handle_security_result() {
+ if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
+
+ const status = this._sock.rQshift32();
+
+ if (status === 0) { // OK
+ this._rfb_init_state = 'ClientInitialisation';
+ Log.Debug('Authentication OK');
+ return this._init_msg();
+ } else {
+ if (this._rfb_version >= 3.8) {
+ this._rfb_init_state = "SecurityReason";
+ this._security_context = "security result";
+ this._security_status = status;
+ return this._init_msg();
+ } else {
+ this.dispatchEvent(new CustomEvent(
+ "securityfailure",
+ { detail: { status: status } }));
+
+ return this._fail("Security handshake failed");
+ }
+ }
+ }
+
+ _negotiate_server_init() {
+ if (this._sock.rQwait("server initialization", 24)) { return false; }
+
+ /* Screen size */
+ const width = this._sock.rQshift16();
+ const height = this._sock.rQshift16();
+
+ /* PIXEL_FORMAT */
+ const bpp = this._sock.rQshift8();
+ const depth = this._sock.rQshift8();
+ const big_endian = this._sock.rQshift8();
+ const true_color = this._sock.rQshift8();
+
+ const red_max = this._sock.rQshift16();
+ const green_max = this._sock.rQshift16();
+ const blue_max = this._sock.rQshift16();
+ const red_shift = this._sock.rQshift8();
+ const green_shift = this._sock.rQshift8();
+ const blue_shift = this._sock.rQshift8();
+ this._sock.rQskipBytes(3); // padding
+
+ // NB(directxman12): we don't want to call any callbacks or print messages until
+ // *after* we're past the point where we could backtrack
+
+ /* Connection name/title */
+ const name_length = this._sock.rQshift32();
+ if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
+ this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
+
+ if (this._rfb_tightvnc) {
+ if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
+ // In TightVNC mode, ServerInit message is extended
+ const numServerMessages = this._sock.rQshift16();
+ const numClientMessages = this._sock.rQshift16();
+ const numEncodings = this._sock.rQshift16();
+ this._sock.rQskipBytes(2); // padding
+
+ const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
+ if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
+
+ // we don't actually do anything with the capability information that TIGHT sends,
+ // so we just skip the all of this.
+
+ // TIGHT server message capabilities
+ this._sock.rQskipBytes(16 * numServerMessages);
+
+ // TIGHT client message capabilities
+ this._sock.rQskipBytes(16 * numClientMessages);
+
+ // TIGHT encoding capabilities
+ this._sock.rQskipBytes(16 * numEncodings);
+ }
+
+ // NB(directxman12): these are down here so that we don't run them multiple times
+ // if we backtrack
+ Log.Info("Screen: " + width + "x" + height +
+ ", bpp: " + bpp + ", depth: " + depth +
+ ", big_endian: " + big_endian +
+ ", true_color: " + true_color +
+ ", red_max: " + red_max +
+ ", green_max: " + green_max +
+ ", blue_max: " + blue_max +
+ ", red_shift: " + red_shift +
+ ", green_shift: " + green_shift +
+ ", blue_shift: " + blue_shift);
+
+ if (big_endian !== 0) {
+ Log.Warn("Server native endian is not little endian");
+ }
+
+ if (red_shift !== 16) {
+ Log.Warn("Server native red-shift is not 16");
+ }
+
+ if (blue_shift !== 0) {
+ Log.Warn("Server native blue-shift is not 0");
+ }
+
+ this._resize(width, height);
+
+ if (!this._viewOnly) { this._keyboard.grab(); }
+ if (!this._viewOnly) { this._mouse.grab(); }
+
+ this._fb_depth = 24;
+
+ if (this._fb_name === "Intel(r) AMT KVM") {
+ Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
+ this._fb_depth = 8;
+ }
+
+ RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
+ this._sendEncodings();
+ RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
+
+ this._updateConnectionState('connected');
+ return true;
+ }
+
+ _sendEncodings() {
+ const encs = [];
+
+ // In preference order
+ encs.push(encodings.encodingCopyRect);
+ // Only supported with full depth support
+ if (this._fb_depth == 24) {
+ encs.push(encodings.encodingTight);
+ encs.push(encodings.encodingTightPNG);
+ encs.push(encodings.encodingHextile);
+ encs.push(encodings.encodingRRE);
+ }
+ encs.push(encodings.encodingRaw);
+
+ // Psuedo-encoding settings
+ encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
+ encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
+
+ encs.push(encodings.pseudoEncodingDesktopSize);
+ encs.push(encodings.pseudoEncodingLastRect);
+ encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
+ encs.push(encodings.pseudoEncodingExtendedDesktopSize);
+ encs.push(encodings.pseudoEncodingXvp);
+ encs.push(encodings.pseudoEncodingFence);
+ encs.push(encodings.pseudoEncodingContinuousUpdates);
+
+ if (this._fb_depth == 24) {
+ encs.push(encodings.pseudoEncodingCursor);
+ }
+
+ RFB.messages.clientEncodings(this._sock, encs);
+ }
+
+ /* RFB protocol initialization states:
+ * ProtocolVersion
+ * Security
+ * Authentication
+ * SecurityResult
+ * ClientInitialization - not triggered by server message
+ * ServerInitialization
+ */
+ _init_msg() {
+ switch (this._rfb_init_state) {
+ case 'ProtocolVersion':
+ return this._negotiate_protocol_version();
+
+ case 'Security':
+ return this._negotiate_security();
+
+ case 'Authentication':
+ return this._negotiate_authentication();
+
+ case 'SecurityResult':
+ return this._handle_security_result();
+
+ case 'SecurityReason':
+ return this._handle_security_reason();
+
+ case 'ClientInitialisation':
+ this._sock.send([0]); // ClientInitialisation for exclusive access
+ this._rfb_init_state = 'ServerInitialisation';
+ return true;
+
+ case 'ServerInitialisation':
+ return this._negotiate_server_init();
+
+ default:
+ return this._fail("Unknown init state (state: " +
+ this._rfb_init_state + ")");
+ }
+ }
+
+ _handle_set_colour_map_msg() {
+ Log.Debug("SetColorMapEntries");
+
+ return this._fail("Unexpected SetColorMapEntries message");
+ }
+
+ _handle_server_cut_text() {
+ Log.Debug("ServerCutText");
+
+ if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
+ this._sock.rQskipBytes(3); // Padding
+ const length = this._sock.rQshift32();
+ if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
+
+ const text = this._sock.rQshiftStr(length);
+
+ if (this._viewOnly) { return true; }
+
+ this.dispatchEvent(new CustomEvent(
+ "clipboard",
+ { detail: { text: text } }));
+
+ return true;
+ }
+
+ _handle_server_fence_msg() {
+ if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
+ this._sock.rQskipBytes(3); // Padding
+ let flags = this._sock.rQshift32();
+ let length = this._sock.rQshift8();
+
+ if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
+
+ if (length > 64) {
+ Log.Warn("Bad payload length (" + length + ") in fence response");
+ length = 64;
+ }
+
+ const payload = this._sock.rQshiftStr(length);
+
+ this._supportsFence = true;
+
+ /*
+ * Fence flags
+ *
+ * (1<<0) - BlockBefore
+ * (1<<1) - BlockAfter
+ * (1<<2) - SyncNext
+ * (1<<31) - Request
+ */
+
+ if (!(flags & (1<<31))) {
+ return this._fail("Unexpected fence response");
+ }
+
+ // Filter out unsupported flags
+ // FIXME: support syncNext
+ flags &= (1<<0) | (1<<1);
+
+ // BlockBefore and BlockAfter are automatically handled by
+ // the fact that we process each incoming message
+ // synchronuosly.
+ RFB.messages.clientFence(this._sock, flags, payload);
+
+ return true;
+ }
+
+ _handle_xvp_msg() {
+ if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
+ this._sock.rQskipBytes(1); // Padding
+ const xvp_ver = this._sock.rQshift8();
+ const xvp_msg = this._sock.rQshift8();
+
+ switch (xvp_msg) {
+ case 0: // XVP_FAIL
+ Log.Error("XVP Operation Failed");
+ break;
+ case 1: // XVP_INIT
+ this._rfb_xvp_ver = xvp_ver;
+ Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
+ this._setCapability("power", true);
+ break;
+ default:
+ this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
+ break;
+ }
+
+ return true;
+ }
+
+ _normal_msg() {
+ let msg_type;
+ if (this._FBU.rects > 0) {
+ msg_type = 0;
+ } else {
+ msg_type = this._sock.rQshift8();
+ }
+
+ let first, ret;
+ switch (msg_type) {
+ case 0: // FramebufferUpdate
+ ret = this._framebufferUpdate();
+ if (ret && !this._enabledContinuousUpdates) {
+ RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
+ this._fb_width, this._fb_height);
+ }
+ return ret;
+
+ case 1: // SetColorMapEntries
+ return this._handle_set_colour_map_msg();
+
+ case 2: // Bell
+ Log.Debug("Bell");
+ this.dispatchEvent(new CustomEvent(
+ "bell",
+ { detail: {} }));
+ return true;
+
+ case 3: // ServerCutText
+ return this._handle_server_cut_text();
+
+ case 150: // EndOfContinuousUpdates
+ first = !this._supportsContinuousUpdates;
+ this._supportsContinuousUpdates = true;
+ this._enabledContinuousUpdates = false;
+ if (first) {
+ this._enabledContinuousUpdates = true;
+ this._updateContinuousUpdates();
+ Log.Info("Enabling continuous updates.");
+ } else {
+ // FIXME: We need to send a framebufferupdaterequest here
+ // if we add support for turning off continuous updates
+ }
+ return true;
+
+ case 248: // ServerFence
+ return this._handle_server_fence_msg();
+
+ case 250: // XVP
+ return this._handle_xvp_msg();
+
+ default:
+ this._fail("Unexpected server message (type " + msg_type + ")");
+ Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
+ return true;
+ }
+ }
+
+ _onFlush() {
+ this._flushing = false;
+ // Resume processing
+ if (this._sock.rQlen > 0) {
+ this._handle_message();
+ }
+ }
+
+ _framebufferUpdate() {
+ if (this._FBU.rects === 0) {
+ if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
+ this._sock.rQskipBytes(1); // Padding
+ this._FBU.rects = this._sock.rQshift16();
+
+ // Make sure the previous frame is fully rendered first
+ // to avoid building up an excessive queue
+ if (this._display.pending()) {
+ this._flushing = true;
+ this._display.flush();
+ return false;
+ }
+ }
+
+ while (this._FBU.rects > 0) {
+ if (this._FBU.encoding === null) {
+ if (this._sock.rQwait("rect header", 12)) { return false; }
+ /* New FramebufferUpdate */
+
+ const hdr = this._sock.rQshiftBytes(12);
+ this._FBU.x = (hdr[0] << 8) + hdr[1];
+ this._FBU.y = (hdr[2] << 8) + hdr[3];
+ this._FBU.width = (hdr[4] << 8) + hdr[5];
+ this._FBU.height = (hdr[6] << 8) + hdr[7];
+ this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
+ (hdr[10] << 8) + hdr[11], 10);
+ }
+
+ if (!this._handleRect()) {
+ return false;
+ }
+
+ this._FBU.rects--;
+ this._FBU.encoding = null;
+ }
+
+ this._display.flip();
+
+ return true; // We finished this FBU
+ }
+
+ _handleRect() {
+ switch (this._FBU.encoding) {
+ case encodings.pseudoEncodingLastRect:
+ this._FBU.rects = 1; // Will be decreased when we return
+ return true;
+
+ case encodings.pseudoEncodingCursor:
+ return this._handleCursor();
+
+ case encodings.pseudoEncodingQEMUExtendedKeyEvent:
+ // Old Safari doesn't support creating keyboard events
+ try {
+ const keyboardEvent = document.createEvent("keyboardEvent");
+ if (keyboardEvent.code !== undefined) {
+ this._qemuExtKeyEventSupported = true;
+ }
+ } catch (err) {
+ // Do nothing
+ }
+ return true;
+
+ case encodings.pseudoEncodingDesktopSize:
+ this._resize(this._FBU.width, this._FBU.height);
+ return true;
+
+ case encodings.pseudoEncodingExtendedDesktopSize:
+ return this._handleExtendedDesktopSize();
+
+ default:
+ return this._handleDataRect();
+ }
+ }
+
+ _handleCursor() {
+ const hotx = this._FBU.x; // hotspot-x
+ const hoty = this._FBU.y; // hotspot-y
+ const w = this._FBU.width;
+ const h = this._FBU.height;
+
+ const pixelslength = w * h * 4;
+ const masklength = Math.ceil(w / 8) * h;
+
+ let bytes = pixelslength + masklength;
+ if (this._sock.rQwait("cursor encoding", bytes)) {
+ return false;
+ }
+
+ // Decode from BGRX pixels + bit mask to RGBA
+ const pixels = this._sock.rQshiftBytes(pixelslength);
+ const mask = this._sock.rQshiftBytes(masklength);
+ let rgba = new Uint8Array(w * h * 4);
+
+ let pix_idx = 0;
+ for (let y = 0; y < h; y++) {
+ for (let x = 0; x < w; x++) {
+ let mask_idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
+ let alpha = (mask[mask_idx] << (x % 8)) & 0x80 ? 255 : 0;
+ rgba[pix_idx ] = pixels[pix_idx + 2];
+ rgba[pix_idx + 1] = pixels[pix_idx + 1];
+ rgba[pix_idx + 2] = pixels[pix_idx];
+ rgba[pix_idx + 3] = alpha;
+ pix_idx += 4;
+ }
+ }
+
+ this._updateCursor(rgba, hotx, hoty, w, h);
+
+ return true;
+ }
+
+ _handleExtendedDesktopSize() {
+ if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
+ return false;
+ }
+
+ const number_of_screens = this._sock.rQpeek8();
+
+ let bytes = 4 + (number_of_screens * 16);
+ if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
+ return false;
+ }
+
+ const firstUpdate = !this._supportsSetDesktopSize;
+ this._supportsSetDesktopSize = true;
+
+ // Normally we only apply the current resize mode after a
+ // window resize event. However there is no such trigger on the
+ // initial connect. And we don't know if the server supports
+ // resizing until we've gotten here.
+ if (firstUpdate) {
+ this._requestRemoteResize();
+ }
+
+ this._sock.rQskipBytes(1); // number-of-screens
+ this._sock.rQskipBytes(3); // padding
+
+ for (let i = 0; i < number_of_screens; i += 1) {
+ // Save the id and flags of the first screen
+ if (i === 0) {
+ this._screen_id = this._sock.rQshiftBytes(4); // id
+ this._sock.rQskipBytes(2); // x-position
+ this._sock.rQskipBytes(2); // y-position
+ this._sock.rQskipBytes(2); // width
+ this._sock.rQskipBytes(2); // height
+ this._screen_flags = this._sock.rQshiftBytes(4); // flags
+ } else {
+ this._sock.rQskipBytes(16);
+ }
+ }
+
+ /*
+ * The x-position indicates the reason for the change:
+ *
+ * 0 - server resized on its own
+ * 1 - this client requested the resize
+ * 2 - another client requested the resize
+ */
+
+ // We need to handle errors when we requested the resize.
+ if (this._FBU.x === 1 && this._FBU.y !== 0) {
+ let msg = "";
+ // The y-position indicates the status code from the server
+ switch (this._FBU.y) {
+ case 1:
+ msg = "Resize is administratively prohibited";
+ break;
+ case 2:
+ msg = "Out of resources";
+ break;
+ case 3:
+ msg = "Invalid screen layout";
+ break;
+ default:
+ msg = "Unknown reason";
+ break;
+ }
+ Log.Warn("Server did not accept the resize request: "
+ + msg);
+ } else {
+ this._resize(this._FBU.width, this._FBU.height);
+ }
+
+ return true;
+ }
+
+ _handleDataRect() {
+ let decoder = this._decoders[this._FBU.encoding];
+ if (!decoder) {
+ this._fail("Unsupported encoding (encoding: " +
+ this._FBU.encoding + ")");
+ return false;
+ }
+
+ try {
+ return decoder.decodeRect(this._FBU.x, this._FBU.y,
+ this._FBU.width, this._FBU.height,
+ this._sock, this._display,
+ this._fb_depth);
+ } catch (err) {
+ this._fail("Error decoding rect: " + err);
+ return false;
+ }
+ }
+
+ _updateContinuousUpdates() {
+ if (!this._enabledContinuousUpdates) { return; }
+
+ RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
+ this._fb_width, this._fb_height);
+ }
+
+ _resize(width, height) {
+ this._fb_width = width;
+ this._fb_height = height;
+
+ this._display.resize(this._fb_width, this._fb_height);
+
+ // Adjust the visible viewport based on the new dimensions
+ this._updateClip();
+ this._updateScale();
+
+ this._updateContinuousUpdates();
+ }
+
+ _xvpOp(ver, op) {
+ if (this._rfb_xvp_ver < ver) { return; }
+ Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
+ RFB.messages.xvpOp(this._sock, ver, op);
+ }
+
+ _updateCursor(rgba, hotx, hoty, w, h) {
+ this._cursorImage = {
+ rgbaPixels: rgba,
+ hotx: hotx, hoty: hoty, w: w, h: h,
+ };
+ this._refreshCursor();
+ }
+
+ _shouldShowDotCursor() {
+ // Called when this._cursorImage is updated
+ if (!this._showDotCursor) {
+ // User does not want to see the dot, so...
+ return false;
+ }
+
+ // The dot should not be shown if the cursor is already visible,
+ // i.e. contains at least one not-fully-transparent pixel.
+ // So iterate through all alpha bytes in rgba and stop at the
+ // first non-zero.
+ for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
+ if (this._cursorImage.rgbaPixels[i]) {
+ return false;
+ }
+ }
+
+ // At this point, we know that the cursor is fully transparent, and
+ // the user wants to see the dot instead of this.
+ return true;
+ }
+
+ _refreshCursor() {
+ const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
+ this._cursor.change(image.rgbaPixels,
+ image.hotx, image.hoty,
+ image.w, image.h
+ );
+ }
+
+ static genDES(password, challenge) {
+ const passwordChars = password.split('').map(c => c.charCodeAt(0));
+ return (new DES(passwordChars)).encrypt(challenge);
+ }
+}
+
+// Class Methods
+RFB.messages = {
+ keyEvent(sock, keysym, down) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 4; // msg-type
+ buff[offset + 1] = down;
+
+ buff[offset + 2] = 0;
+ buff[offset + 3] = 0;
+
+ buff[offset + 4] = (keysym >> 24);
+ buff[offset + 5] = (keysym >> 16);
+ buff[offset + 6] = (keysym >> 8);
+ buff[offset + 7] = keysym;
+
+ sock._sQlen += 8;
+ sock.flush();
+ },
+
+ QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
+ function getRFBkeycode(xt_scancode) {
+ const upperByte = (keycode >> 8);
+ const lowerByte = (keycode & 0x00ff);
+ if (upperByte === 0xe0 && lowerByte < 0x7f) {
+ return lowerByte | 0x80;
+ }
+ return xt_scancode;
+ }
+
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 255; // msg-type
+ buff[offset + 1] = 0; // sub msg-type
+
+ buff[offset + 2] = (down >> 8);
+ buff[offset + 3] = down;
+
+ buff[offset + 4] = (keysym >> 24);
+ buff[offset + 5] = (keysym >> 16);
+ buff[offset + 6] = (keysym >> 8);
+ buff[offset + 7] = keysym;
+
+ const RFBkeycode = getRFBkeycode(keycode);
+
+ buff[offset + 8] = (RFBkeycode >> 24);
+ buff[offset + 9] = (RFBkeycode >> 16);
+ buff[offset + 10] = (RFBkeycode >> 8);
+ buff[offset + 11] = RFBkeycode;
+
+ sock._sQlen += 12;
+ sock.flush();
+ },
+
+ pointerEvent(sock, x, y, mask) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 5; // msg-type
+
+ buff[offset + 1] = mask;
+
+ buff[offset + 2] = x >> 8;
+ buff[offset + 3] = x;
+
+ buff[offset + 4] = y >> 8;
+ buff[offset + 5] = y;
+
+ sock._sQlen += 6;
+ sock.flush();
+ },
+
+ // TODO(directxman12): make this unicode compatible?
+ clientCutText(sock, text) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 6; // msg-type
+
+ buff[offset + 1] = 0; // padding
+ buff[offset + 2] = 0; // padding
+ buff[offset + 3] = 0; // padding
+
+ let length = text.length;
+
+ buff[offset + 4] = length >> 24;
+ buff[offset + 5] = length >> 16;
+ buff[offset + 6] = length >> 8;
+ buff[offset + 7] = length;
+
+ sock._sQlen += 8;
+
+ // We have to keep track of from where in the text we begin creating the
+ // buffer for the flush in the next iteration.
+ let textOffset = 0;
+
+ let remaining = length;
+ while (remaining > 0) {
+
+ let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
+ for (let i = 0; i < flushSize; i++) {
+ buff[sock._sQlen + i] = text.charCodeAt(textOffset + i);
+ }
+
+ sock._sQlen += flushSize;
+ sock.flush();
+
+ remaining -= flushSize;
+ textOffset += flushSize;
+ }
+ },
+
+ setDesktopSize(sock, width, height, id, flags) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 251; // msg-type
+ buff[offset + 1] = 0; // padding
+ buff[offset + 2] = width >> 8; // width
+ buff[offset + 3] = width;
+ buff[offset + 4] = height >> 8; // height
+ buff[offset + 5] = height;
+
+ buff[offset + 6] = 1; // number-of-screens
+ buff[offset + 7] = 0; // padding
+
+ // screen array
+ buff[offset + 8] = id >> 24; // id
+ buff[offset + 9] = id >> 16;
+ buff[offset + 10] = id >> 8;
+ buff[offset + 11] = id;
+ buff[offset + 12] = 0; // x-position
+ buff[offset + 13] = 0;
+ buff[offset + 14] = 0; // y-position
+ buff[offset + 15] = 0;
+ buff[offset + 16] = width >> 8; // width
+ buff[offset + 17] = width;
+ buff[offset + 18] = height >> 8; // height
+ buff[offset + 19] = height;
+ buff[offset + 20] = flags >> 24; // flags
+ buff[offset + 21] = flags >> 16;
+ buff[offset + 22] = flags >> 8;
+ buff[offset + 23] = flags;
+
+ sock._sQlen += 24;
+ sock.flush();
+ },
+
+ clientFence(sock, flags, payload) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 248; // msg-type
+
+ buff[offset + 1] = 0; // padding
+ buff[offset + 2] = 0; // padding
+ buff[offset + 3] = 0; // padding
+
+ buff[offset + 4] = flags >> 24; // flags
+ buff[offset + 5] = flags >> 16;
+ buff[offset + 6] = flags >> 8;
+ buff[offset + 7] = flags;
+
+ const n = payload.length;
+
+ buff[offset + 8] = n; // length
+
+ for (let i = 0; i < n; i++) {
+ buff[offset + 9 + i] = payload.charCodeAt(i);
+ }
+
+ sock._sQlen += 9 + n;
+ sock.flush();
+ },
+
+ enableContinuousUpdates(sock, enable, x, y, width, height) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 150; // msg-type
+ buff[offset + 1] = enable; // enable-flag
+
+ buff[offset + 2] = x >> 8; // x
+ buff[offset + 3] = x;
+ buff[offset + 4] = y >> 8; // y
+ buff[offset + 5] = y;
+ buff[offset + 6] = width >> 8; // width
+ buff[offset + 7] = width;
+ buff[offset + 8] = height >> 8; // height
+ buff[offset + 9] = height;
+
+ sock._sQlen += 10;
+ sock.flush();
+ },
+
+ pixelFormat(sock, depth, true_color) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ let bpp;
+
+ if (depth > 16) {
+ bpp = 32;
+ } else if (depth > 8) {
+ bpp = 16;
+ } else {
+ bpp = 8;
+ }
+
+ const bits = Math.floor(depth/3);
+
+ buff[offset] = 0; // msg-type
+
+ buff[offset + 1] = 0; // padding
+ buff[offset + 2] = 0; // padding
+ buff[offset + 3] = 0; // padding
+
+ buff[offset + 4] = bpp; // bits-per-pixel
+ buff[offset + 5] = depth; // depth
+ buff[offset + 6] = 0; // little-endian
+ buff[offset + 7] = true_color ? 1 : 0; // true-color
+
+ buff[offset + 8] = 0; // red-max
+ buff[offset + 9] = (1 << bits) - 1; // red-max
+
+ buff[offset + 10] = 0; // green-max
+ buff[offset + 11] = (1 << bits) - 1; // green-max
+
+ buff[offset + 12] = 0; // blue-max
+ buff[offset + 13] = (1 << bits) - 1; // blue-max
+
+ buff[offset + 14] = bits * 2; // red-shift
+ buff[offset + 15] = bits * 1; // green-shift
+ buff[offset + 16] = bits * 0; // blue-shift
+
+ buff[offset + 17] = 0; // padding
+ buff[offset + 18] = 0; // padding
+ buff[offset + 19] = 0; // padding
+
+ sock._sQlen += 20;
+ sock.flush();
+ },
+
+ clientEncodings(sock, encodings) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 2; // msg-type
+ buff[offset + 1] = 0; // padding
+
+ buff[offset + 2] = encodings.length >> 8;
+ buff[offset + 3] = encodings.length;
+
+ let j = offset + 4;
+ for (let i = 0; i < encodings.length; i++) {
+ const enc = encodings[i];
+ buff[j] = enc >> 24;
+ buff[j + 1] = enc >> 16;
+ buff[j + 2] = enc >> 8;
+ buff[j + 3] = enc;
+
+ j += 4;
+ }
+
+ sock._sQlen += j - offset;
+ sock.flush();
+ },
+
+ fbUpdateRequest(sock, incremental, x, y, w, h) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ if (typeof(x) === "undefined") { x = 0; }
+ if (typeof(y) === "undefined") { y = 0; }
+
+ buff[offset] = 3; // msg-type
+ buff[offset + 1] = incremental ? 1 : 0;
+
+ buff[offset + 2] = (x >> 8) & 0xFF;
+ buff[offset + 3] = x & 0xFF;
+
+ buff[offset + 4] = (y >> 8) & 0xFF;
+ buff[offset + 5] = y & 0xFF;
+
+ buff[offset + 6] = (w >> 8) & 0xFF;
+ buff[offset + 7] = w & 0xFF;
+
+ buff[offset + 8] = (h >> 8) & 0xFF;
+ buff[offset + 9] = h & 0xFF;
+
+ sock._sQlen += 10;
+ sock.flush();
+ },
+
+ xvpOp(sock, ver, op) {
+ const buff = sock._sQ;
+ const offset = sock._sQlen;
+
+ buff[offset] = 250; // msg-type
+ buff[offset + 1] = 0; // padding
+
+ buff[offset + 2] = ver;
+ buff[offset + 3] = op;
+
+ sock._sQlen += 4;
+ sock.flush();
+ }
+};
+
+RFB.cursors = {
+ none: {
+ rgbaPixels: new Uint8Array(),
+ w: 0, h: 0,
+ hotx: 0, hoty: 0,
+ },
+
+ dot: {
+ /* eslint-disable indent */
+ rgbaPixels: new Uint8Array([
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
+ 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
+ ]),
+ /* eslint-enable indent */
+ w: 3, h: 3,
+ hotx: 1, hoty: 1,
+ }
+};
diff --git a/systemvm/agent/noVNC/core/util/browser.js b/systemvm/agent/noVNC/core/util/browser.js
new file mode 100644
index 0000000..8996cfe
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/browser.js
@@ -0,0 +1,90 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+import * as Log from './logging.js';
+
+// Touch detection
+export let isTouchDevice = ('ontouchstart' in document.documentElement) ||
+ // requried for Chrome debugger
+ (document.ontouchstart !== undefined) ||
+ // required for MS Surface
+ (navigator.maxTouchPoints > 0) ||
+ (navigator.msMaxTouchPoints > 0);
+window.addEventListener('touchstart', function onFirstTouch() {
+ isTouchDevice = true;
+ window.removeEventListener('touchstart', onFirstTouch, false);
+}, false);
+
+
+// The goal is to find a certain physical width, the devicePixelRatio
+// brings us a bit closer but is not optimal.
+export let dragThreshold = 10 * (window.devicePixelRatio || 1);
+
+let _supportsCursorURIs = false;
+
+try {
+ const target = document.createElement('canvas');
+ target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
+
+ if (target.style.cursor) {
+ Log.Info("Data URI scheme cursor supported");
+ _supportsCursorURIs = true;
+ } else {
+ Log.Warn("Data URI scheme cursor not supported");
+ }
+} catch (exc) {
+ Log.Error("Data URI scheme cursor test exception: " + exc);
+}
+
+export const supportsCursorURIs = _supportsCursorURIs;
+
+let _supportsImageMetadata = false;
+try {
+ new ImageData(new Uint8ClampedArray(4), 1, 1);
+ _supportsImageMetadata = true;
+} catch (ex) {
+ // ignore failure
+}
+export const supportsImageMetadata = _supportsImageMetadata;
+
+export function isMac() {
+ return navigator && !!(/mac/i).exec(navigator.platform);
+}
+
+export function isWindows() {
+ return navigator && !!(/win/i).exec(navigator.platform);
+}
+
+export function isIOS() {
+ return navigator &&
+ (!!(/ipad/i).exec(navigator.platform) ||
+ !!(/iphone/i).exec(navigator.platform) ||
+ !!(/ipod/i).exec(navigator.platform));
+}
+
+export function isAndroid() {
+ return navigator && !!(/android/i).exec(navigator.userAgent);
+}
+
+export function isSafari() {
+ return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
+ navigator.userAgent.indexOf('Chrome') === -1);
+}
+
+export function isIE() {
+ return navigator && !!(/trident/i).exec(navigator.userAgent);
+}
+
+export function isEdge() {
+ return navigator && !!(/edge/i).exec(navigator.userAgent);
+}
+
+export function isFirefox() {
+ return navigator && !!(/firefox/i).exec(navigator.userAgent);
+}
+
diff --git a/systemvm/agent/noVNC/core/util/cursor.js b/systemvm/agent/noVNC/core/util/cursor.js
new file mode 100644
index 0000000..0d0b754
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/cursor.js
@@ -0,0 +1,221 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+import { supportsCursorURIs, isTouchDevice } from './browser.js';
+
+const useFallback = !supportsCursorURIs || isTouchDevice;
+
+export default class Cursor {
+ constructor() {
+ this._target = null;
+
+ this._canvas = document.createElement('canvas');
+
+ if (useFallback) {
+ this._canvas.style.position = 'fixed';
+ this._canvas.style.zIndex = '65535';
+ this._canvas.style.pointerEvents = 'none';
+ // Can't use "display" because of Firefox bug #1445997
+ this._canvas.style.visibility = 'hidden';
+ document.body.appendChild(this._canvas);
+ }
+
+ this._position = { x: 0, y: 0 };
+ this._hotSpot = { x: 0, y: 0 };
+
+ this._eventHandlers = {
+ 'mouseover': this._handleMouseOver.bind(this),
+ 'mouseleave': this._handleMouseLeave.bind(this),
+ 'mousemove': this._handleMouseMove.bind(this),
+ 'mouseup': this._handleMouseUp.bind(this),
+ 'touchstart': this._handleTouchStart.bind(this),
+ 'touchmove': this._handleTouchMove.bind(this),
+ 'touchend': this._handleTouchEnd.bind(this),
+ };
+ }
+
+ attach(target) {
+ if (this._target) {
+ this.detach();
+ }
+
+ this._target = target;
+
+ if (useFallback) {
+ // FIXME: These don't fire properly except for mouse
+ /// movement in IE. We want to also capture element
+ // movement, size changes, visibility, etc.
+ const options = { capture: true, passive: true };
+ this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
+ this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
+ this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
+ this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
+
+ // There is no "touchleave" so we monitor touchstart globally
+ window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
+ this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
+ this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
+ }
+
+ this.clear();
+ }
+
+ detach() {
+ if (useFallback) {
+ const options = { capture: true, passive: true };
+ this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
+ this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
+ this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
+ this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
+
+ window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
+ this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
+ this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
+ }
+
+ this._target = null;
+ }
+
+ change(rgba, hotx, hoty, w, h) {
+ if ((w === 0) || (h === 0)) {
+ this.clear();
+ return;
+ }
+
+ this._position.x = this._position.x + this._hotSpot.x - hotx;
+ this._position.y = this._position.y + this._hotSpot.y - hoty;
+ this._hotSpot.x = hotx;
+ this._hotSpot.y = hoty;
+
+ let ctx = this._canvas.getContext('2d');
+
+ this._canvas.width = w;
+ this._canvas.height = h;
+
+ let img;
+ try {
+ // IE doesn't support this
+ img = new ImageData(new Uint8ClampedArray(rgba), w, h);
+ } catch (ex) {
+ img = ctx.createImageData(w, h);
+ img.data.set(new Uint8ClampedArray(rgba));
+ }
+ ctx.clearRect(0, 0, w, h);
+ ctx.putImageData(img, 0, 0);
+
+ if (useFallback) {
+ this._updatePosition();
+ } else {
+ let url = this._canvas.toDataURL();
+ this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
+ }
+ }
+
+ clear() {
+ this._target.style.cursor = 'none';
+ this._canvas.width = 0;
+ this._canvas.height = 0;
+ this._position.x = this._position.x + this._hotSpot.x;
+ this._position.y = this._position.y + this._hotSpot.y;
+ this._hotSpot.x = 0;
+ this._hotSpot.y = 0;
+ }
+
+ _handleMouseOver(event) {
+ // This event could be because we're entering the target, or
+ // moving around amongst its sub elements. Let the move handler
+ // sort things out.
+ this._handleMouseMove(event);
+ }
+
+ _handleMouseLeave(event) {
+ this._hideCursor();
+ }
+
+ _handleMouseMove(event) {
+ this._updateVisibility(event.target);
+
+ this._position.x = event.clientX - this._hotSpot.x;
+ this._position.y = event.clientY - this._hotSpot.y;
+
+ this._updatePosition();
+ }
+
+ _handleMouseUp(event) {
+ // We might get this event because of a drag operation that
+ // moved outside of the target. Check what's under the cursor
+ // now and adjust visibility based on that.
+ let target = document.elementFromPoint(event.clientX, event.clientY);
+ this._updateVisibility(target);
+ }
+
+ _handleTouchStart(event) {
+ // Just as for mouseover, we let the move handler deal with it
+ this._handleTouchMove(event);
+ }
+
+ _handleTouchMove(event) {
+ this._updateVisibility(event.target);
+
+ this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
+ this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
+
+ this._updatePosition();
+ }
+
+ _handleTouchEnd(event) {
+ // Same principle as for mouseup
+ let target = document.elementFromPoint(event.changedTouches[0].clientX,
+ event.changedTouches[0].clientY);
+ this._updateVisibility(target);
+ }
+
+ _showCursor() {
+ if (this._canvas.style.visibility === 'hidden') {
+ this._canvas.style.visibility = '';
+ }
+ }
+
+ _hideCursor() {
+ if (this._canvas.style.visibility !== 'hidden') {
+ this._canvas.style.visibility = 'hidden';
+ }
+ }
+
+ // Should we currently display the cursor?
+ // (i.e. are we over the target, or a child of the target without a
+ // different cursor set)
+ _shouldShowCursor(target) {
+ // Easy case
+ if (target === this._target) {
+ return true;
+ }
+ // Other part of the DOM?
+ if (!this._target.contains(target)) {
+ return false;
+ }
+ // Has the child its own cursor?
+ // FIXME: How can we tell that a sub element has an
+ // explicit "cursor: none;"?
+ if (window.getComputedStyle(target).cursor !== 'none') {
+ return false;
+ }
+ return true;
+ }
+
+ _updateVisibility(target) {
+ if (this._shouldShowCursor(target)) {
+ this._showCursor();
+ } else {
+ this._hideCursor();
+ }
+ }
+
+ _updatePosition() {
+ this._canvas.style.left = this._position.x + "px";
+ this._canvas.style.top = this._position.y + "px";
+ }
+}
diff --git a/systemvm/agent/noVNC/core/util/events.js b/systemvm/agent/noVNC/core/util/events.js
new file mode 100644
index 0000000..f122279
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/events.js
@@ -0,0 +1,139 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Cross-browser event and position routines
+ */
+
+export function getPointerEvent(e) {
+ return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
+}
+
+export function stopEvent(e) {
+ e.stopPropagation();
+ e.preventDefault();
+}
+
+// Emulate Element.setCapture() when not supported
+let _captureRecursion = false;
+let _captureElem = null;
+function _captureProxy(e) {
+ // Recursion protection as we'll see our own event
+ if (_captureRecursion) return;
+
+ // Clone the event as we cannot dispatch an already dispatched event
+ const newEv = new e.constructor(e.type, e);
+
+ _captureRecursion = true;
+ _captureElem.dispatchEvent(newEv);
+ _captureRecursion = false;
+
+ // Avoid double events
+ e.stopPropagation();
+
+ // Respect the wishes of the redirected event handlers
+ if (newEv.defaultPrevented) {
+ e.preventDefault();
+ }
+
+ // Implicitly release the capture on button release
+ if (e.type === "mouseup") {
+ releaseCapture();
+ }
+}
+
+// Follow cursor style of target element
+function _captureElemChanged() {
+ const captureElem = document.getElementById("noVNC_mouse_capture_elem");
+ captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
+}
+
+const _captureObserver = new MutationObserver(_captureElemChanged);
+
+let _captureIndex = 0;
+
+export function setCapture(elem) {
+ if (elem.setCapture) {
+
+ elem.setCapture();
+
+ // IE releases capture on 'click' events which might not trigger
+ elem.addEventListener('mouseup', releaseCapture);
+
+ } else {
+ // Release any existing capture in case this method is
+ // called multiple times without coordination
+ releaseCapture();
+
+ let captureElem = document.getElementById("noVNC_mouse_capture_elem");
+
+ if (captureElem === null) {
+ captureElem = document.createElement("div");
+ captureElem.id = "noVNC_mouse_capture_elem";
+ captureElem.style.position = "fixed";
+ captureElem.style.top = "0px";
+ captureElem.style.left = "0px";
+ captureElem.style.width = "100%";
+ captureElem.style.height = "100%";
+ captureElem.style.zIndex = 10000;
+ captureElem.style.display = "none";
+ document.body.appendChild(captureElem);
+
+ // This is to make sure callers don't get confused by having
+ // our blocking element as the target
+ captureElem.addEventListener('contextmenu', _captureProxy);
+
+ captureElem.addEventListener('mousemove', _captureProxy);
+ captureElem.addEventListener('mouseup', _captureProxy);
+ }
+
+ _captureElem = elem;
+ _captureIndex++;
+
+ // Track cursor and get initial cursor
+ _captureObserver.observe(elem, {attributes: true});
+ _captureElemChanged();
+
+ captureElem.style.display = "";
+
+ // We listen to events on window in order to keep tracking if it
+ // happens to leave the viewport
+ window.addEventListener('mousemove', _captureProxy);
+ window.addEventListener('mouseup', _captureProxy);
+ }
+}
+
+export function releaseCapture() {
+ if (document.releaseCapture) {
+
+ document.releaseCapture();
+
+ } else {
+ if (!_captureElem) {
+ return;
+ }
+
+ // There might be events already queued, so we need to wait for
+ // them to flush. E.g. contextmenu in Microsoft Edge
+ window.setTimeout((expected) => {
+ // Only clear it if it's the expected grab (i.e. no one
+ // else has initiated a new grab)
+ if (_captureIndex === expected) {
+ _captureElem = null;
+ }
+ }, 0, _captureIndex);
+
+ _captureObserver.disconnect();
+
+ const captureElem = document.getElementById("noVNC_mouse_capture_elem");
+ captureElem.style.display = "none";
+
+ window.removeEventListener('mousemove', _captureProxy);
+ window.removeEventListener('mouseup', _captureProxy);
+ }
+}
diff --git a/systemvm/agent/noVNC/core/util/eventtarget.js b/systemvm/agent/noVNC/core/util/eventtarget.js
new file mode 100644
index 0000000..f54ca9b
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/eventtarget.js
@@ -0,0 +1,35 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+export default class EventTargetMixin {
+ constructor() {
+ this._listeners = new Map();
+ }
+
+ addEventListener(type, callback) {
+ if (!this._listeners.has(type)) {
+ this._listeners.set(type, new Set());
+ }
+ this._listeners.get(type).add(callback);
+ }
+
+ removeEventListener(type, callback) {
+ if (this._listeners.has(type)) {
+ this._listeners.get(type).delete(callback);
+ }
+ }
+
+ dispatchEvent(event) {
+ if (!this._listeners.has(event.type)) {
+ return true;
+ }
+ this._listeners.get(event.type)
+ .forEach(callback => callback.call(this, event));
+ return !event.defaultPrevented;
+ }
+}
diff --git a/systemvm/agent/noVNC/core/util/logging.js b/systemvm/agent/noVNC/core/util/logging.js
new file mode 100644
index 0000000..4c8943d
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/logging.js
@@ -0,0 +1,56 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Logging/debug routines
+ */
+
+let _log_level = 'warn';
+
+let Debug = () => {};
+let Info = () => {};
+let Warn = () => {};
+let Error = () => {};
+
+export function init_logging(level) {
+ if (typeof level === 'undefined') {
+ level = _log_level;
+ } else {
+ _log_level = level;
+ }
+
+ Debug = Info = Warn = Error = () => {};
+
+ if (typeof window.console !== "undefined") {
+ /* eslint-disable no-console, no-fallthrough */
+ switch (level) {
+ case 'debug':
+ Debug = console.debug.bind(window.console);
+ case 'info':
+ Info = console.info.bind(window.console);
+ case 'warn':
+ Warn = console.warn.bind(window.console);
+ case 'error':
+ Error = console.error.bind(window.console);
+ case 'none':
+ break;
+ default:
+ throw new window.Error("invalid logging type '" + level + "'");
+ }
+ /* eslint-enable no-console, no-fallthrough */
+ }
+}
+
+export function get_logging() {
+ return _log_level;
+}
+
+export { Debug, Info, Warn, Error };
+
+// Initialize logging level
+init_logging();
diff --git a/systemvm/agent/noVNC/core/util/polyfill.js b/systemvm/agent/noVNC/core/util/polyfill.js
new file mode 100644
index 0000000..648ceeb
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/polyfill.js
@@ -0,0 +1,54 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/* Polyfills to provide new APIs in old browsers */
+
+/* Object.assign() (taken from MDN) */
+if (typeof Object.assign != 'function') {
+ // Must be writable: true, enumerable: false, configurable: true
+ Object.defineProperty(Object, "assign", {
+ value: function assign(target, varArgs) { // .length of function is 2
+ 'use strict';
+ if (target == null) { // TypeError if undefined or null
+ throw new TypeError('Cannot convert undefined or null to object');
+ }
+
+ const to = Object(target);
+
+ for (let index = 1; index < arguments.length; index++) {
+ const nextSource = arguments[index];
+
+ if (nextSource != null) { // Skip over if undefined or null
+ for (let nextKey in nextSource) {
+ // Avoid bugs when hasOwnProperty is shadowed
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ },
+ writable: true,
+ configurable: true
+ });
+}
+
+/* CustomEvent constructor (taken from MDN) */
+(() => {
+ function CustomEvent(event, params) {
+ params = params || { bubbles: false, cancelable: false, detail: undefined };
+ const evt = document.createEvent( 'CustomEvent' );
+ evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
+ return evt;
+ }
+
+ CustomEvent.prototype = window.Event.prototype;
+
+ if (typeof window.CustomEvent !== "function") {
+ window.CustomEvent = CustomEvent;
+ }
+})();
diff --git a/systemvm/agent/noVNC/core/util/strings.js b/systemvm/agent/noVNC/core/util/strings.js
new file mode 100644
index 0000000..61f4f23
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/strings.js
@@ -0,0 +1,14 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Decode from UTF-8
+ */
+export function decodeUTF8(utf8string) {
+ return decodeURIComponent(escape(utf8string));
+}
diff --git a/systemvm/agent/noVNC/core/websock.js b/systemvm/agent/noVNC/core/websock.js
new file mode 100644
index 0000000..51b9a66
--- /dev/null
+++ b/systemvm/agent/noVNC/core/websock.js
@@ -0,0 +1,290 @@
+/*
+ * Websock: high-performance binary WebSockets
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * Websock is similar to the standard WebSocket object but with extra
+ * buffer handling.
+ *
+ * Websock has built-in receive queue buffering; the message event
+ * does not contain actual data but is simply a notification that
+ * there is new data available. Several rQ* methods are available to
+ * read binary data off of the receive queue.
+ */
+
+import * as Log from './util/logging.js';
+
+// this has performance issues in some versions Chromium, and
+// doesn't gain a tremendous amount of performance increase in Firefox
+// at the moment. It may be valuable to turn it on in the future.
+const ENABLE_COPYWITHIN = false;
+const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
+
+export default class Websock {
+ constructor() {
+ this._websocket = null; // WebSocket object
+
+ this._rQi = 0; // Receive queue index
+ this._rQlen = 0; // Next write position in the receive queue
+ this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
+ this._rQmax = this._rQbufferSize / 8;
+ // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
+ this._rQ = null; // Receive queue
+
+ this._sQbufferSize = 1024 * 10; // 10 KiB
+ // called in init: this._sQ = new Uint8Array(this._sQbufferSize);
+ this._sQlen = 0;
+ this._sQ = null; // Send queue
+
+ this._eventHandlers = {
+ message: () => {},
+ open: () => {},
+ close: () => {},
+ error: () => {}
+ };
+ }
+
+ // Getters and Setters
+ get sQ() {
+ return this._sQ;
+ }
+
+ get rQ() {
+ return this._rQ;
+ }
+
+ get rQi() {
+ return this._rQi;
+ }
+
+ set rQi(val) {
+ this._rQi = val;
+ }
+
+ // Receive Queue
+ get rQlen() {
+ return this._rQlen - this._rQi;
+ }
+
+ rQpeek8() {
+ return this._rQ[this._rQi];
+ }
+
+ rQskipBytes(bytes) {
+ this._rQi += bytes;
+ }
+
+ rQshift8() {
+ return this._rQshift(1);
+ }
+
+ rQshift16() {
+ return this._rQshift(2);
+ }
+
+ rQshift32() {
+ return this._rQshift(4);
+ }
+
+ // TODO(directxman12): test performance with these vs a DataView
+ _rQshift(bytes) {
+ let res = 0;
+ for (let byte = bytes - 1; byte >= 0; byte--) {
+ res += this._rQ[this._rQi++] << (byte * 8);
+ }
+ return res;
+ }
+
+ rQshiftStr(len) {
+ if (typeof(len) === 'undefined') { len = this.rQlen; }
+ let str = "";
+ // Handle large arrays in steps to avoid long strings on the stack
+ for (let i = 0; i < len; i += 4096) {
+ let part = this.rQshiftBytes(Math.min(4096, len - i));
+ str += String.fromCharCode.apply(null, part);
+ }
+ return str;
+ }
+
+ rQshiftBytes(len) {
+ if (typeof(len) === 'undefined') { len = this.rQlen; }
+ this._rQi += len;
+ return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
+ }
+
+ rQshiftTo(target, len) {
+ if (len === undefined) { len = this.rQlen; }
+ // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
+ target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
+ this._rQi += len;
+ }
+
+ rQslice(start, end = this.rQlen) {
+ return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
+ }
+
+ // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
+ // to be available in the receive queue. Return true if we need to
+ // wait (and possibly print a debug message), otherwise false.
+ rQwait(msg, num, goback) {
+ if (this.rQlen < num) {
+ if (goback) {
+ if (this._rQi < goback) {
+ throw new Error("rQwait cannot backup " + goback + " bytes");
+ }
+ this._rQi -= goback;
+ }
+ return true; // true means need more data
+ }
+ return false;
+ }
+
+ // Send Queue
+
+ flush() {
+ if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
+ this._websocket.send(this._encode_message());
+ this._sQlen = 0;
+ }
+ }
+
+ send(arr) {
+ this._sQ.set(arr, this._sQlen);
+ this._sQlen += arr.length;
+ this.flush();
+ }
+
+ send_string(str) {
+ this.send(str.split('').map(chr => chr.charCodeAt(0)));
+ }
+
+ // Event Handlers
+ off(evt) {
+ this._eventHandlers[evt] = () => {};
+ }
+
+ on(evt, handler) {
+ this._eventHandlers[evt] = handler;
+ }
+
+ _allocate_buffers() {
+ this._rQ = new Uint8Array(this._rQbufferSize);
+ this._sQ = new Uint8Array(this._sQbufferSize);
+ }
+
+ init() {
+ this._allocate_buffers();
+ this._rQi = 0;
+ this._websocket = null;
+ }
+
+ open(uri, protocols) {
+ this.init();
+
+ this._websocket = new WebSocket(uri, protocols);
+ this._websocket.binaryType = 'arraybuffer';
+
+ this._websocket.onmessage = this._recv_message.bind(this);
+ this._websocket.onopen = () => {
+ Log.Debug('>> WebSock.onopen');
+ if (this._websocket.protocol) {
+ Log.Info("Server choose sub-protocol: " + this._websocket.protocol);
+ }
+
+ this._eventHandlers.open();
+ Log.Debug("<< WebSock.onopen");
+ };
+ this._websocket.onclose = (e) => {
+ Log.Debug(">> WebSock.onclose");
+ this._eventHandlers.close(e);
+ Log.Debug("<< WebSock.onclose");
+ };
+ this._websocket.onerror = (e) => {
+ Log.Debug(">> WebSock.onerror: " + e);
+ this._eventHandlers.error(e);
+ Log.Debug("<< WebSock.onerror: " + e);
+ };
+ }
+
+ close() {
+ if (this._websocket) {
+ if ((this._websocket.readyState === WebSocket.OPEN) ||
+ (this._websocket.readyState === WebSocket.CONNECTING)) {
+ Log.Info("Closing WebSocket connection");
+ this._websocket.close();
+ }
+
+ this._websocket.onmessage = () => {};
+ }
+ }
+
+ // private methods
+ _encode_message() {
+ // Put in a binary arraybuffer
+ // according to the spec, you can send ArrayBufferViews with the send method
+ return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
+ }
+
+ _expand_compact_rQ(min_fit) {
+ const resizeNeeded = min_fit || this.rQlen > this._rQbufferSize / 2;
+ if (resizeNeeded) {
+ if (!min_fit) {
+ // just double the size if we need to do compaction
+ this._rQbufferSize *= 2;
+ } else {
+ // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
+ this._rQbufferSize = (this.rQlen + min_fit) * 8;
+ }
+ }
+
+ // we don't want to grow unboundedly
+ if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
+ this._rQbufferSize = MAX_RQ_GROW_SIZE;
+ if (this._rQbufferSize - this.rQlen < min_fit) {
+ throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
+ }
+ }
+
+ if (resizeNeeded) {
+ const old_rQbuffer = this._rQ.buffer;
+ this._rQmax = this._rQbufferSize / 8;
+ this._rQ = new Uint8Array(this._rQbufferSize);
+ this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
+ } else {
+ if (ENABLE_COPYWITHIN) {
+ this._rQ.copyWithin(0, this._rQi);
+ } else {
+ this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
+ }
+ }
+
+ this._rQlen = this._rQlen - this._rQi;
+ this._rQi = 0;
+ }
+
+ _decode_message(data) {
+ // push arraybuffer values onto the end
+ const u8 = new Uint8Array(data);
+ if (u8.length > this._rQbufferSize - this._rQlen) {
+ this._expand_compact_rQ(u8.length);
+ }
+ this._rQ.set(u8, this._rQlen);
+ this._rQlen += u8.length;
+ }
+
+ _recv_message(e) {
+ this._decode_message(e.data);
+ if (this.rQlen > 0) {
+ this._eventHandlers.message();
+ // Compact the receive queue
+ if (this._rQlen == this._rQi) {
+ this._rQlen = 0;
+ this._rQi = 0;
+ } else if (this._rQlen > this._rQmax) {
+ this._expand_compact_rQ();
+ }
+ } else {
+ Log.Debug("Ignoring empty message");
+ }
+ }
+}
diff --git a/systemvm/agent/noVNC/docs/API-internal.md b/systemvm/agent/noVNC/docs/API-internal.md
new file mode 100644
index 0000000..0b29afb
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/API-internal.md
@@ -0,0 +1,122 @@
+# 1. Internal Modules
+
+The noVNC client is composed of several internal modules that handle
+rendering, input, networking, etc. Each of the modules is designed to
+be cross-browser and independent from each other.
+
+Note however that the API of these modules is not guaranteed to be
+stable, and this documentation is not maintained as well as the
+official external API.
+
+
+## 1.1 Module List
+
+* __Mouse__ (core/input/mouse.js): Mouse input event handler with
+limited touch support.
+
+* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
+non-US keyboard support. Translates keyDown and keyUp events to X11
+keysym values.
+
+* __Display__ (core/display.js): Efficient 2D rendering abstraction
+layered on the HTML5 canvas element.
+
+* __Websock__ (core/websock.js): Websock client from websockify
+with transparent binary data support.
+[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page.
+
+
+## 1.2 Callbacks
+
+For the Mouse, Keyboard and Display objects the callback functions are
+assigned to configuration attributes, just as for the RFB object. The
+WebSock module has a method named 'on' that takes two parameters: the
+callback event name, and the callback function.
+
+## 2. Modules
+
+## 2.1 Mouse Module
+
+### 2.1.1 Configuration Attributes
+
+| name | type | mode | default | description
+| ----------- | ---- | ---- | -------- | ------------
+| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks.
+
+### 2.1.2 Methods
+
+| name | parameters | description
+| ------ | ---------- | ------------
+| grab | () | Begin capturing mouse events
+| ungrab | () | Stop capturing mouse events
+
+### 2.1.2 Callbacks
+
+| name | parameters | description
+| ------------- | ------------------- | ------------
+| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release
+| onmousemove | (x, y) | Handler for mouse movement
+
+
+## 2.2 Keyboard Module
+
+### 2.2.1 Configuration Attributes
+
+None
+
+### 2.2.2 Methods
+
+| name | parameters | description
+| ------ | ---------- | ------------
+| grab | () | Begin capturing keyboard events
+| ungrab | () | Stop capturing keyboard events
+
+### 2.2.3 Callbacks
+
+| name | parameters | description
+| ---------- | -------------------- | ------------
+| onkeypress | (keysym, code, down) | Handler for key press/release
+
+
+## 2.3 Display Module
+
+### 2.3.1 Configuration Attributes
+
+| name | type | mode | default | description
+| ------------ | ----- | ---- | ------- | ------------
+| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data}
+| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0
+| clipViewport | bool | RW | false | Use viewport clipping
+| width | int | RO | | Display area width
+| height | int | RO | | Display area height
+
+### 2.3.2 Methods
+
+| name | parameters | description
+| ------------------ | ------------------------------------------------------- | ------------
+| viewportChangePos | (deltaX, deltaY) | Move the viewport relative to the current location
+| viewportChangeSize | (width, height) | Change size of the viewport
+| absX | (x) | Return X relative to the remote display
+| absY | (y) | Return Y relative to the remote display
+| resize | (width, height) | Set width and height
+| flip | (from_queue) | Update the visible canvas with the contents of the rendering canvas
+| clear | () | Clear the display (show logo if set)
+| pending | () | Check if there are waiting items in the render queue
+| flush | () | Resume processing the render queue unless it's empty
+| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle
+| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area
+| imageRect | (x, y, mime, arr) | Draw a rectangle with an image
+| startTile | (x, y, width, height, color) | Begin updating a tile
+| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile
+| finishTile | () | Draw the current tile to the display
+| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
+| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display
+| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display
+| drawImage | (img, x, y) | Draw image and track damage
+| autoscale | (containerWidth, containerHeight) | Scale the display
+
+### 2.3.3 Callbacks
+
+| name | parameters | description
+| ------- | ---------- | ------------
+| onflush | () | A display flush has been requested and we are now ready to resume FBU processing
diff --git a/systemvm/agent/noVNC/docs/API.md b/systemvm/agent/noVNC/docs/API.md
new file mode 100644
index 0000000..d587429
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/API.md
@@ -0,0 +1,375 @@
+# noVNC API
+
+The interface of the noVNC client consists of a single RFB object that
+is instantiated once per connection.
+
+## RFB
+
+The `RFB` object represents a single connection to a VNC server. It
+communicates using a WebSocket that must provide a standard RFB
+protocol stream.
+
+### Constructor
+
+[`RFB()`](#rfb-1)
+ - Creates and returns a new `RFB` object.
+
+### Properties
+
+`viewOnly`
+ - Is a `boolean` indicating if any events (e.g. key presses or mouse
+ movement) should be prevented from being sent to the server.
+ Disabled by default.
+
+`focusOnClick`
+ - Is a `boolean` indicating if keyboard focus should automatically be
+ moved to the remote session when a `mousedown` or `touchstart`
+ event is received.
+
+`touchButton`
+ - Is a `long` controlling the button mask that should be simulated
+ when a touch event is recieved. Uses the same values as
+ [`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
+ Is set to `1` by default.
+
+`clipViewport`
+ - Is a `boolean` indicating if the remote session should be clipped
+ to its container. When disabled scrollbars will be shown to handle
+ the resulting overflow. Disabled by default.
+
+`dragViewport`
+ - Is a `boolean` indicating if mouse events should control the
+ relative position of a clipped remote session. Only relevant if
+ `clipViewport` is enabled. Disabled by default.
+
+`scaleViewport`
+ - Is a `boolean` indicating if the remote session should be scaled
+ locally so it fits its container. When disabled it will be centered
+ if the remote session is smaller than its container, or handled
+ according to `clipViewport` if it is larger. Disabled by default.
+
+`resizeSession`
+ - Is a `boolean` indicating if a request to resize the remote session
+ should be sent whenever the container changes dimensions. Disabled
+ by default.
+
+`showDotCursor`
+ - Is a `boolean` indicating whether a dot cursor should be shown
+ instead of a zero-sized or fully-transparent cursor if the server
+ sets such invisible cursor. Disabled by default.
+
+`background`
+ - Is a valid CSS [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background)
+ style value indicating which background style should be applied
+ to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
+ (solid gray color).
+
+`capabilities` *Read only*
+ - Is an `Object` indicating which optional extensions are available
+ on the server. Some methods may only be called if the corresponding
+ capability is set. The following capabilities are defined:
+
+ | name | type | description
+ | -------- | --------- | -----------
+ | `power` | `boolean` | Machine power control is available
+
+### Events
+
+[`connect`](#connect)
+ - The `connect` event is fired when the `RFB` object has completed
+ the connection and handshaking with the server.
+
+[`disconnect`](#disconnected)
+ - The `disconnect` event is fired when the `RFB` object disconnects.
+
+[`credentialsrequired`](#credentialsrequired)
+ - The `credentialsrequired` event is fired when more credentials must
+ be given to continue.
+
+[`securityfailure`](#securityfailure)
+ - The `securityfailure` event is fired when the security negotiation
+ with the server fails.
+
+[`clipboard`](#clipboard)
+ - The `clipboard` event is fired when clipboard data is received from
+ the server.
+
+[`bell`](#bell)
+ - The `bell` event is fired when a audible bell request is received
+ from the server.
+
+[`desktopname`](#desktopname)
+ - The `desktopname` event is fired when the remote desktop name
+ changes.
+
+[`capabilities`](#capabilities)
+ - The `capabilities` event is fired when `RFB.capabilities` is
+ updated.
+
+### Methods
+
+[`RFB.disconnect()`](#rfbdisconnect)
+ - Disconnect from the server.
+
+[`RFB.sendCredentials()`](#rfbsendcredentials)
+ - Send credentials to server. Should be called after the
+ [`credentialsrequired`](#credentialsrequired) event has fired.
+
+[`RFB.sendKey()`](#rfbsendKey)
+ - Send a key event.
+
+[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
+ - Send Ctrl-Alt-Del key sequence.
+
+[`RFB.focus()`](#rfbfocus)
+ - Move keyboard focus to the remote session.
+
+[`RFB.blur()`](#rfbblur)
+ - Remove keyboard focus from the remote session.
+
+[`RFB.machineShutdown()`](#rfbmachineshutdown)
+ - Request a shutdown of the remote machine.
+
+[`RFB.machineReboot()`](#rfbmachinereboot)
+ - Request a reboot of the remote machine.
+
+[`RFB.machineReset()`](#rfbmachinereset)
+ - Request a reset of the remote machine.
+
+[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
+ - Send clipboard contents to server.
+
+### Details
+
+#### RFB()
+
+The `RFB()` constructor returns a new `RFB` object and initiates a new
+connection to a specified VNC server.
+
+##### Syntax
+
+ let rfb = new RFB( target, url [, options] );
+
+###### Parameters
+
+**`target`**
+ - A block [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement)
+ that specifies where the `RFB` object should attach itself. The
+ existing contents of the `HTMLElement` will be untouched, but new
+ elements will be added during the lifetime of the `RFB` object.
+
+**`url`**
+ - A `DOMString` specifying the VNC server to connect to. This must be
+ a valid WebSocket URL.
+
+**`options`** *Optional*
+ - An `Object` specifying extra details about how the connection
+ should be made.
+
+ Possible options:
+
+ `shared`
+ - A `boolean` indicating if the remote server should be shared or
+ if any other connected clients should be disconnected. Enabled
+ by default.
+
+ `credentials`
+ - An `Object` specifying the credentials to provide to the server
+ when authenticating. The following credentials are possible:
+
+ | name | type | description
+ | ------------ | ----------- | -----------
+ | `"username"` | `DOMString` | The user that authenticates
+ | `"password"` | `DOMString` | Password for the user
+ | `"target"` | `DOMString` | Target machine or session
+
+ `repeaterID`
+ - A `DOMString` specifying the ID to provide to any VNC repeater
+ encountered.
+
+#### connect
+
+The `connect` event is fired after all the handshaking with the server
+is completed and the connection is fully established. After this event
+the `RFB` object is ready to recieve graphics updates and to send input.
+
+#### disconnect
+
+The `disconnect` event is fired when the connection has been
+terminated. The `detail` property is an `Object` that contains the
+property `clean`. `clean` is a `boolean` indicating if the termination
+was clean or not. In the event of an unexpected termination or an error
+`clean` will be set to false.
+
+#### credentialsrequired
+
+The `credentialsrequired` event is fired when the server requests more
+credentials than were specified to [`RFB()`](#rfb-1). The `detail`
+property is an `Object` containing the property `types` which is an
+`Array` of `DOMString` listing the credentials that are required.
+
+#### securityfailure
+
+The `securityfailure` event is fired when the handshaking process with
+the server fails during the security negotiation step. The `detail`
+property is an `Object` containing the following properties:
+
+| Property | Type | Description
+| -------- | ----------- | -----------
+| `status` | `long` | The failure status code
+| `reason` | `DOMString` | The **optional** reason for the failure
+
+The property `status` corresponds to the
+[SecurityResult](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult)
+status code in cases of failure. A status of zero will not be sent in
+this event since that indicates a successful security handshaking
+process. The optional property `reason` is provided by the server and
+thus the language of the string is not known. However most servers will
+probably send English strings. The server can choose to not send a
+reason and in these cases the `reason` property will be omitted.
+
+#### clipboard
+
+The `clipboard` event is fired when the server has sent clipboard data.
+The `detail` property is an `Object` containing the property `text`
+which is a `DOMString` with the clipboard data.
+
+#### bell
+
+The `bell` event is fired when the server has requested an audible
+bell.
+
+#### desktopname
+
+The `desktopname` event is fired when the name of the remote desktop
+changes. The `detail` property is an `Object` with the property `name`
+which is a `DOMString` specifying the new name.
+
+#### capabilities
+
+The `capabilities` event is fired whenever an entry is added or removed
+from `RFB.capabilities`. The `detail` property is an `Object` with the
+property `capabilities` containing the new value of `RFB.capabilities`.
+
+#### RFB.disconnect()
+
+The `RFB.disconnect()` method is used to disconnect from the currently
+connected server.
+
+##### Syntax
+
+ RFB.disconnect( );
+
+#### RFB.sendCredentials()
+
+The `RFB.sendCredentials()` method is used to provide the missing
+credentials after a `credentialsrequired` event has been fired.
+
+##### Syntax
+
+ RFB.sendCredentials( credentials );
+
+###### Parameters
+
+**`credentials`**
+ - An `Object` specifying the credentials to provide to the server
+ when authenticating. See [`RFB()`](#rfb-1) for details.
+
+#### RFB.sendKey()
+
+The `RFB.sendKey()` method is used to send a key event to the server.
+
+##### Syntax
+
+ RFB.sendKey( keysym, code [, down] );
+
+###### Parameters
+
+**`keysym`**
+ - A `long` specifying the RFB keysym to send. Can be `0` if a valid
+ **`code`** is specified.
+
+**`code`**
+ - A `DOMString` specifying the physical key to send. Valid values are
+ those that can be specified to
+ [`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code).
+ If the physical key cannot be determined then `null` shall be
+ specified.
+
+**`down`** *Optional*
+ - A `boolean` specifying if a press or a release event should be
+ sent. If omitted then both a press and release event are sent.
+
+#### RFB.sendCtrlAltDel()
+
+The `RFB.sendCtrlAltDel()` method is used to send the key sequence
+*left Control*, *left Alt*, *Delete*. This is a convenience wrapper
+around [`RFB.sendKey()`](#rfbsendkey).
+
+##### Syntax
+
+ RFB.sendCtrlAltDel( );
+
+#### RFB.focus()
+
+The `RFB.focus()` method sets the keyboard focus on the remote session.
+Keyboard events will be sent to the remote server after this point.
+
+##### Syntax
+
+ RFB.focus( );
+
+#### RFB.blur()
+
+The `RFB.blur()` method remove keyboard focus on the remote session.
+Keyboard events will no longer be sent to the remote server after this
+point.
+
+##### Syntax
+
+ RFB.blur( );
+
+#### RFB.machineShutdown()
+
+The `RFB.machineShutdown()` method is used to request to shut down the
+remote machine. The capability `power` must be set for this method to
+have any effect.
+
+##### Syntax
+
+ RFB.machineShutdown( );
+
+#### RFB.machineReboot()
+
+The `RFB.machineReboot()` method is used to request a clean reboot of
+the remote machine. The capability `power` must be set for this method
+to have any effect.
+
+##### Syntax
+
+ RFB.machineReboot( );
+
+#### RFB.machineReset()
+
+The `RFB.machineReset()` method is used to request a forced reset of
+the remote machine. The capability `power` must be set for this method
+to have any effect.
+
+##### Syntax
+
+ RFB.machineReset( );
+
+#### RFB.clipboardPasteFrom()
+
+The `RFB.clipboardPasteFrom()` method is used to send clipboard data
+to the remote server.
+
+##### Syntax
+
+ RFB.clipboardPasteFrom( text );
+
+###### Parameters
+
+**`text`**
+ - A `DOMString` specifying the clipboard data to send. Currently only
+ characters from ISO 8859-1 are supported.
diff --git a/systemvm/agent/noVNC/docs/EMBEDDING.md b/systemvm/agent/noVNC/docs/EMBEDDING.md
new file mode 100644
index 0000000..5399b48
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/EMBEDDING.md
@@ -0,0 +1,119 @@
+# Embedding and Deploying noVNC Application
+
+This document describes how to embed and deploy the noVNC application, which
+includes settings and a full user interface. If you are looking for
+documentation on how to use the core noVNC library in your own application,
+then please see our [library documentation](LIBRARY.md).
+
+## Files
+
+The noVNC application consists of the following files and directories:
+
+* `vnc.html` - The main page for the application and where users should go. It
+ is possible to rename this file.
+
+* `app/` - Support files for the application. Contains code, images, styles and
+ translations.
+
+* `core/` - The core noVNC library.
+
+* `vendor/` - Third party support libraries used by the application and the
+ core library.
+
+The most basic deployment consists of simply serving these files from a web
+server and setting up a WebSocket proxy to the VNC server.
+
+## Parameters
+
+The noVNC application can be controlled by including certain settings in the
+query string. Currently the following options are available:
+
+* `autoconnect` - Automatically connect as soon as the page has finished
+ loading.
+
+* `reconnect` - If noVNC should automatically reconnect if the connection is
+ dropped.
+
+* `reconnect_delay` - How long to wait in milliseconds before attempting to
+ reconnect.
+
+* `host` - The WebSocket host to connect to.
+
+* `port` - The WebSocket port to connect to.
+
+* `encrypt` - If TLS should be used for the WebSocket connection.
+
+* `path` - The WebSocket path to use.
+
+* `password` - The password sent to the server, if required.
+
+* `repeaterID` - The repeater ID to use if a VNC repeater is detected.
+
+* `shared` - If other VNC clients should be disconnected when noVNC connects.
+
+* `bell` - If the keyboard bell should be enabled or not.
+
+* `view_only` - If the remote session should be in non-interactive mode.
+
+* `view_clip` - If the remote session should be clipped or use scrollbars if
+ it cannot fit in the browser.
+
+* `resize` - How to resize the remote session if it is not the same size as
+ the browser window. Can be one of `off`, `scale` and `remote`.
+
+* `show_dot` - If a dot cursor should be shown when the remote server provides
+ no local cursor, or provides a fully-transparent (invisible) cursor.
+
+* `logging` - The console log level. Can be one of `error`, `warn`, `info` or
+ `debug`.
+
+## Pre-conversion of Modules
+
+noVNC is written using ECMAScript 6 modules. Many of the major browsers support
+these modules natively, but not all. By default the noVNC application includes
+a script that can convert these modules to an older format as they are being
+loaded. However this process can be slow and severely increases the load time
+for the application.
+
+It is possible to perform this conversion ahead of time, avoiding the extra
+load times. To do this please follow these steps:
+
+ 1. Install Node.js
+ 2. Run `npm install` in the noVNC directory
+ 3. Run `./utils/use_require.js --with-app --as commonjs`
+
+This will produce a `build/` directory that includes everything needed to run
+the noVNC application.
+
+## HTTP Serving Considerations
+### Browser Cache Issue
+
+If you serve noVNC files using a web server that provides an ETag header, and
+include any options in the query string, a nasty browser cache issue can bite
+you on upgrade, resulting in a red error box. The issue is caused by a mismatch
+between the new vnc.html (which is reloaded because the user has used it with
+new query string after the upgrade) and the old javascript files (that the
+browser reuses from its cache). To avoid this issue, the browser must be told
+to always revalidate cached files using conditional requests. The correct
+semantics are achieved via the (confusingly named) `Cache-Control: no-cache`
+header that needs to be provided in the web server responses.
+
+### Example Server Configurations
+
+Apache:
+
+```
+ # In the main configuration file
+ # (Debian/Ubuntu users: use "a2enmod headers" instead)
+ LoadModule headers_module modules/mod_headers.so
+
+ # In the <Directory> or <Location> block related to noVNC
+ Header set Cache-Control "no-cache"
+```
+
+Nginx:
+
+```
+ # In the location block related to noVNC
+ add_header Cache-Control no-cache;
+```
diff --git a/systemvm/agent/noVNC/docs/LIBRARY.md b/systemvm/agent/noVNC/docs/LIBRARY.md
new file mode 100644
index 0000000..63f55e8
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/LIBRARY.md
@@ -0,0 +1,35 @@
+# Using the noVNC JavaScript library
+
+This document describes how to make use of the noVNC JavaScript library for
+integration in your own VNC client application. If you wish to embed the more
+complete noVNC application with its included user interface then please see
+our [embedding documentation](EMBEDDING.md).
+
+## API
+
+The API of noVNC consists of a single object called `RFB`. The formal
+documentation for that object can be found in our [API documentation](API.md).
+
+## Example
+
+noVNC includes a small example application called `vnc_lite.html`. This does
+not make use of all the features of noVNC, but is a good start to see how to
+do things.
+
+## Conversion of Modules
+
+noVNC is written using ECMAScript 6 modules. Many of the major browsers support
+these modules natively, but not all. They are also not supported by Node.js. To
+use noVNC in these places the library must first be converted.
+
+Fortunately noVNC includes a script to handle this conversion. Please follow
+the following steps:
+
+ 1. Install Node.js
+ 2. Run `npm install` in the noVNC directory
+ 3. Run `./utils/use_require.js --as <module format>`
+
+Several module formats are available. Please run
+`./utils/use_require.js --help` to see them all.
+
+The result of the conversion is available in the `lib/` directory.
diff --git a/systemvm/agent/noVNC/docs/LICENSE.BSD-2-Clause b/systemvm/agent/noVNC/docs/LICENSE.BSD-2-Clause
new file mode 100644
index 0000000..9d66ec9
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/LICENSE.BSD-2-Clause
@@ -0,0 +1,22 @@
+Copyright (c) <year>, <copyright holder>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/systemvm/agent/noVNC/docs/LICENSE.BSD-3-Clause b/systemvm/agent/noVNC/docs/LICENSE.BSD-3-Clause
new file mode 100644
index 0000000..e160466
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/LICENSE.BSD-3-Clause
@@ -0,0 +1,24 @@
+Copyright (c) <year>, <copyright holder>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/systemvm/agent/noVNC/docs/LICENSE.MPL-2.0 b/systemvm/agent/noVNC/docs/LICENSE.MPL-2.0
new file mode 100644
index 0000000..14e2f77
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/LICENSE.MPL-2.0
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/systemvm/agent/noVNC/docs/LICENSE.OFL-1.1 b/systemvm/agent/noVNC/docs/LICENSE.OFL-1.1
new file mode 100644
index 0000000..77b1731
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/LICENSE.OFL-1.1
@@ -0,0 +1,91 @@
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/systemvm/agent/noVNC/docs/flash_policy.txt b/systemvm/agent/noVNC/docs/flash_policy.txt
new file mode 100644
index 0000000..df325c0
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/flash_policy.txt
@@ -0,0 +1,4 @@
+Manual setup:
+
+DATA="echo \'<cross-domain-policy><allow-access-from domain=\\\"*\\\" to-ports=\\\"*\\\" /></cross-domain-policy>\'"
+/usr/bin/socat -T 1 TCP-L:843,reuseaddr,fork,crlf SYSTEM:"$DATA"
diff --git a/systemvm/agent/noVNC/docs/links b/systemvm/agent/noVNC/docs/links
new file mode 100644
index 0000000..31544ce
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/links
@@ -0,0 +1,76 @@
+New tight PNG protocol:
+ http://wiki.qemu.org/VNC_Tight_PNG
+ http://xf.iksaif.net/blog/index.php?post/2010/06/14/QEMU:-Tight-PNG-and-some-profiling
+
+RFB protocol and extensions:
+ http://tigervnc.org/cgi-bin/rfbproto
+
+Canvas Browser Compatibility:
+ http://philip.html5.org/tests/canvas/suite/tests/results.html
+
+WebSockets API standard:
+ http://www.whatwg.org/specs/web-apps/current-work/complete.html#websocket
+ http://dev.w3.org/html5/websockets/
+ http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
+
+Browser Keyboard Events detailed:
+ http://unixpapa.com/js/key.html
+
+ActionScript (Flash) WebSocket implementation:
+ http://github.com/gimite/web-socket-js
+
+ActionScript (Flash) crypto/TLS library:
+ http://code.google.com/p/as3crypto
+ http://github.com/lyokato/as3crypto_patched
+
+TLS Protocol:
+ http://en.wikipedia.org/wiki/Transport_Layer_Security
+
+Generate self-signed certificate:
+ http://docs.python.org/dev/library/ssl.html#certificates
+
+Cursor appearance/style (for Cursor pseudo-encoding):
+ http://en.wikipedia.org/wiki/ICO_(file_format)
+ http://www.daubnet.com/en/file-format-cur
+ https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property
+ http://www.fileformat.info/format/bmp/egff.htm
+
+Icon/Cursor file format:
+ http://msdn.microsoft.com/en-us/library/ms997538
+ http://msdn.microsoft.com/en-us/library/aa921550.aspx
+ http://msdn.microsoft.com/en-us/library/aa930622.aspx
+
+
+RDP Protocol specification:
+ http://msdn.microsoft.com/en-us/library/cc240445(v=PROT.10).aspx
+
+
+Related projects:
+
+ guacamole: http://guacamole.sourceforge.net/
+
+ - Web client, but Java servlet does pre-processing
+
+ jsvnc: http://code.google.com/p/jsvnc/
+
+ - No releases
+
+ webvnc: http://code.google.com/p/webvnc/
+
+ - Jetty web server gateway, no updates since April 2008.
+
+ RealVNC Java applet: http://www.realvnc.com/support/javavncviewer.html
+
+ - Java applet
+
+ Flashlight-VNC: http://www.wizhelp.com/flashlight-vnc/
+
+ - Adobe Flash implementation
+
+ FVNC: http://osflash.org/fvnc
+
+ - Adbove Flash implementation
+
+ CanVNC: http://canvnc.sourceforge.net/
+
+ - HTML client with REST to VNC python proxy. Mostly vapor.
diff --git a/systemvm/agent/noVNC/docs/notes b/systemvm/agent/noVNC/docs/notes
new file mode 100644
index 0000000..dfef0bd
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/notes
@@ -0,0 +1,5 @@
+Rebuilding inflator.js
+
+- Download pako from npm
+- Install browserify using npm
+- browserify core/inflator.mod.js -o core/inflator.js -s Inflator
diff --git a/systemvm/agent/noVNC/docs/rfb_notes b/systemvm/agent/noVNC/docs/rfb_notes
new file mode 100644
index 0000000..643e16c
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/rfb_notes
@@ -0,0 +1,147 @@
+5.1.1 ProtocolVersion: 12, 12 bytes
+
+ - Sent by server, max supported
+ 12 ascii - "RFB 003.008\n"
+ - Response by client, version to use
+ 12 ascii - "RFB 003.003\n"
+
+5.1.2 Authentication: >=4, [16, 4] bytes
+
+ - Sent by server
+ CARD32 - authentication-scheme
+ 0 - connection failed
+ CARD32 - length
+ length - reason
+ 1 - no authentication
+
+ 2 - VNC authentication
+ 16 CARD8 - challenge (random bytes)
+
+ - Response by client (if VNC authentication)
+ 16 CARD8 - client encrypts the challenge with DES, using user
+ password as key, sends resulting 16 byte response
+
+ - Response by server (if VNC authentication)
+ CARD32 - 0 - OK
+ 1 - failed
+ 2 - too-many
+
+5.1.3 ClientInitialisation: 1 byte
+ - Sent by client
+ CARD8 - shared-flag, 0 exclusive, non-zero shared
+
+5.1.4 ServerInitialisation: >=24 bytes
+ - Sent by server
+ CARD16 - framebuffer-width
+ CARD16 - framebuffer-height
+ 16 byte PIXEL_FORMAT - server-pixel-format
+ CARD8 - bits-per-pixel
+ CARD8 - depth
+ CARD8 - big-endian-flag, non-zero is big endian
+ CARD8 - true-color-flag, non-zero then next 6 apply
+ CARD16 - red-max
+ CARD16 - green-max
+ CARD16 - blue-max
+ CARD8 - red-shift
+ CARD8 - green-shift
+ CARD8 - blue-shift
+ 3 bytes - padding
+ CARD32 - name-length
+
+ CARD8[length] - name-string
+
+
+
+Client to Server Messages:
+
+5.2.1 SetPixelFormat: 20 bytes
+ CARD8: 0 - message-type
+ ...
+
+5.2.2 FixColourMapEntries: >=6 bytes
+ CARD8: 1 - message-type
+ ...
+
+5.2.3 SetEncodings: >=8 bytes
+ CARD8: 2 - message-type
+ CARD8 - padding
+ CARD16 - numer-of-encodings
+
+ CARD32 - encoding-type in preference order
+ 0 - raw
+ 1 - copy-rectangle
+ 2 - RRE
+ 4 - CoRRE
+ 5 - hextile
+
+5.2.4 FramebufferUpdateRequest (10 bytes)
+ CARD8: 3 - message-type
+ CARD8 - incremental (0 for full-update, non-zero for incremental)
+ CARD16 - x-position
+ CARD16 - y-position
+ CARD16 - width
+ CARD16 - height
+
+
+5.2.5 KeyEvent: 8 bytes
+ CARD8: 4 - message-type
+ CARD8 - down-flag
+ 2 bytes - padding
+ CARD32 - key (X-Windows keysym values)
+
+5.2.6 PointerEvent: 6 bytes
+ CARD8: 5 - message-type
+ CARD8 - button-mask
+ CARD16 - x-position
+ CARD16 - y-position
+
+5.2.7 ClientCutText: >=9 bytes
+ CARD8: 6 - message-type
+ ...
+
+
+Server to Client Messages:
+
+5.3.1 FramebufferUpdate
+ CARD8: 0 - message-type
+ 1 byte - padding
+ CARD16 - number-of-rectangles
+
+ CARD16 - x-position
+ CARD16 - y-position
+ CARD16 - width
+ CARD16 - height
+ CARD16 - encoding-type:
+ 0 - raw
+ 1 - copy rectangle
+ 2 - RRE
+ 4 - CoRRE
+ 5 - hextile
+
+ raw:
+ - width x height pixel values
+
+ copy rectangle:
+ CARD16 - src-x-position
+ CARD16 - src-y-position
+
+ RRE:
+ CARD32 - N number-of-subrectangles
+ Nxd bytes - background-pixel-value (d bits-per-pixel)
+
+ ...
+
+5.3.2 SetColourMapEntries (no support)
+ CARD8: 1 - message-type
+ ...
+
+5.3.3 Bell
+ CARD8: 2 - message-type
+
+5.3.4 ServerCutText
+ CARD8: 3 - message-type
+
+
+
+
+
diff --git a/systemvm/agent/noVNC/docs/rfbproto-3.3.pdf b/systemvm/agent/noVNC/docs/rfbproto-3.3.pdf
new file mode 100644
index 0000000..56b8764
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/rfbproto-3.3.pdf
Binary files differ
diff --git a/systemvm/agent/noVNC/docs/rfbproto-3.7.pdf b/systemvm/agent/noVNC/docs/rfbproto-3.7.pdf
new file mode 100644
index 0000000..1ef5462
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/rfbproto-3.7.pdf
Binary files differ
diff --git a/systemvm/agent/noVNC/docs/rfbproto-3.8.pdf b/systemvm/agent/noVNC/docs/rfbproto-3.8.pdf
new file mode 100644
index 0000000..8f0730f
--- /dev/null
+++ b/systemvm/agent/noVNC/docs/rfbproto-3.8.pdf
Binary files differ
diff --git a/systemvm/agent/noVNC/karma.conf.js b/systemvm/agent/noVNC/karma.conf.js
new file mode 100644
index 0000000..5cbd7a5
--- /dev/null
+++ b/systemvm/agent/noVNC/karma.conf.js
@@ -0,0 +1,134 @@
+// Karma configuration
+
+module.exports = (config) => {
+ const customLaunchers = {};
+ let browsers = [];
+ let useSauce = false;
+
+ // use Sauce when running on Travis
+ if (process.env.TRAVIS_JOB_NUMBER) {
+ useSauce = true;
+ }
+
+ if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') {
+ const names = process.env.TEST_BROWSER_NAME.split(',');
+ const platforms = process.env.TEST_BROWSER_OS.split(',');
+ const versions = process.env.TEST_BROWSER_VERSION
+ ? process.env.TEST_BROWSER_VERSION.split(',')
+ : [null];
+
+ for (let i = 0; i < names.length; i++) {
+ for (let j = 0; j < platforms.length; j++) {
+ for (let k = 0; k < versions.length; k++) {
+ let launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i];
+ if (versions[k]) {
+ launcher_name += '_' + versions[k];
+ }
+
+ customLaunchers[launcher_name] = {
+ base: 'SauceLabs',
+ browserName: names[i],
+ platform: platforms[j],
+ };
+
+ if (versions[i]) {
+ customLaunchers[launcher_name].version = versions[k];
+ }
+ }
+ }
+ }
+
+ browsers = Object.keys(customLaunchers);
+ } else {
+ useSauce = false;
+ //browsers = ['PhantomJS'];
+ browsers = [];
+ }
+
+ const my_conf = {
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['mocha', 'sinon-chai'],
+
+ // list of files / patterns to load in the browser (loaded in order)
+ files: [
+ { pattern: 'app/localization.js', included: false },
+ { pattern: 'app/webutil.js', included: false },
+ { pattern: 'core/**/*.js', included: false },
+ { pattern: 'vendor/pako/**/*.js', included: false },
+ { pattern: 'vendor/browser-es-module-loader/dist/*.js*', included: false },
+ { pattern: 'tests/test.*.js', included: false },
+ { pattern: 'tests/fake.*.js', included: false },
+ { pattern: 'tests/assertions.js', included: false },
+ 'vendor/promise.js',
+ 'tests/karma-test-main.js',
+ ],
+
+ client: {
+ mocha: {
+ // replace Karma debug page with mocha display
+ 'reporter': 'html',
+ 'ui': 'bdd'
+ }
+ },
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+ customLaunchers: customLaunchers,
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: browsers,
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['mocha'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: true,
+
+ // Increase timeout in case connection is slow/we run more browsers than possible
+ // (we currently get 3 for free, and we try to run 7, so it can take a while)
+ captureTimeout: 240000,
+
+ // similarly to above
+ browserNoActivityTimeout: 100000,
+ };
+
+ if (useSauce) {
+ my_conf.reporters.push('saucelabs');
+ my_conf.captureTimeout = 0; // use SL timeout
+ my_conf.sauceLabs = {
+ testName: 'noVNC Tests (all)',
+ startConnect: false,
+ tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
+ };
+ }
+
+ config.set(my_conf);
+};
diff --git a/systemvm/agent/noVNC/package.json b/systemvm/agent/noVNC/package.json
new file mode 100644
index 0000000..2d84a5f
--- /dev/null
+++ b/systemvm/agent/noVNC/package.json
@@ -0,0 +1,81 @@
+{
+ "name": "@novnc/novnc",
+ "version": "1.1.0",
+ "description": "An HTML5 VNC client",
+ "browser": "lib/rfb",
+ "directories": {
+ "lib": "lib",
+ "doc": "docs",
+ "test": "tests"
+ },
+ "files": [
+ "lib",
+ "AUTHORS",
+ "VERSION",
+ "docs/API.md",
+ "docs/LIBRARY.md",
+ "docs/LICENSE*",
+ "core",
+ "vendor/pako"
+ ],
+ "scripts": {
+ "lint": "eslint app core po tests utils",
+ "test": "karma start karma.conf.js",
+ "prepublish": "node ./utils/use_require.js --as commonjs --clean"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/novnc/noVNC.git"
+ },
+ "author": "Joel Martin <github@martintribe.org> (https://github.com/kanaka)",
+ "contributors": [
+ "Solly Ross <sross@redhat.com> (https://github.com/directxman12)",
+ "Peter Åstrand <astrand@cendio.se> (https://github.com/astrand)",
+ "Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)",
+ "Pierre Ossman <ossman@cendio.se> (https://github.com/CendioOssman)"
+ ],
+ "license": "MPL-2.0",
+ "bugs": {
+ "url": "https://github.com/novnc/noVNC/issues"
+ },
+ "homepage": "https://github.com/novnc/noVNC",
+ "devDependencies": {
+ "babel-core": "^6.22.1",
+ "babel-plugin-add-module-exports": "^0.2.1",
+ "babel-plugin-import-redirect": "*",
+ "babel-plugin-syntax-dynamic-import": "^6.18.0",
+ "babel-plugin-transform-es2015-modules-amd": "^6.22.0",
+ "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0",
+ "babel-plugin-transform-es2015-modules-systemjs": "^6.22.0",
+ "babel-plugin-transform-es2015-modules-umd": "^6.22.0",
+ "babel-preset-es2015": "^6.24.1",
+ "babelify": "^7.3.0",
+ "browserify": "^13.1.0",
+ "chai": "^3.5.0",
+ "commander": "^2.9.0",
+ "es-module-loader": "^2.1.0",
+ "eslint": "^4.16.0",
+ "fs-extra": "^1.0.0",
+ "jsdom": "*",
+ "karma": "^1.3.0",
+ "karma-mocha": "^1.3.0",
+ "karma-mocha-reporter": "^2.2.0",
+ "karma-sauce-launcher": "^1.0.0",
+ "karma-sinon-chai": "^2.0.0",
+ "mocha": "^3.1.2",
+ "node-getopt": "*",
+ "po2json": "*",
+ "requirejs": "^2.3.2",
+ "rollup": "^0.41.4",
+ "rollup-plugin-node-resolve": "^2.0.0",
+ "sinon": "^4.0.0",
+ "sinon-chai": "^2.8.0"
+ },
+ "dependencies": {},
+ "keywords": [
+ "vnc",
+ "rfb",
+ "novnc",
+ "websockify"
+ ]
+}
diff --git a/systemvm/agent/noVNC/po/Makefile b/systemvm/agent/noVNC/po/Makefile
new file mode 100644
index 0000000..6dbd830
--- /dev/null
+++ b/systemvm/agent/noVNC/po/Makefile
@@ -0,0 +1,35 @@
+all:
+.PHONY: update-po update-js update-pot
+
+LINGUAS := cs de el es ko nl pl ru sv tr zh_CN zh_TW
+
+VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
+
+POFILES := $(addsuffix .po,$(LINGUAS))
+JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS)))
+
+update-po: $(POFILES)
+update-js: $(JSONFILES)
+
+%.po: noVNC.pot
+ msgmerge --update --lang=$* $@ $<
+../app/locale/%.json: %.po
+ ./po2js $< $@
+
+update-pot:
+ xgettext --output=noVNC.js.pot \
+ --copyright-holder="The noVNC Authors" \
+ --package-name="noVNC" \
+ --package-version="$(VERSION)" \
+ --msgid-bugs-address="novnc@googlegroups.com" \
+ --add-comments=TRANSLATORS: \
+ --from-code=UTF-8 \
+ --sort-by-file \
+ ../app/*.js \
+ ../core/*.js \
+ ../core/input/*.js
+ ./xgettext-html --output=noVNC.html.pot \
+ ../vnc.html
+ msgcat --output-file=noVNC.pot \
+ --sort-by-file noVNC.js.pot noVNC.html.pot
+ rm -f noVNC.js.pot noVNC.html.pot
diff --git a/systemvm/agent/noVNC/po/cs.po b/systemvm/agent/noVNC/po/cs.po
new file mode 100644
index 0000000..2b1efd8
--- /dev/null
+++ b/systemvm/agent/noVNC/po/cs.po
@@ -0,0 +1,294 @@
+# Czech translations for noVNC package.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Petr <petr@kle.cz>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.0.0-testing.2\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2018-10-19 12:00+0200\n"
+"PO-Revision-Date: 2018-10-19 12:00+0200\n"
+"Last-Translator: Petr <petr@kle.cz>\n"
+"Language-Team: Czech\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#: ../app/ui.js:389
+msgid "Connecting..."
+msgstr "Připojení..."
+
+#: ../app/ui.js:396
+msgid "Disconnecting..."
+msgstr "Odpojení..."
+
+#: ../app/ui.js:402
+msgid "Reconnecting..."
+msgstr "Obnova připojení..."
+
+#: ../app/ui.js:407
+msgid "Internal error"
+msgstr "Vnitřní chyba"
+
+#: ../app/ui.js:997
+msgid "Must set host"
+msgstr "Hostitel musí být nastavení"
+
+#: ../app/ui.js:1079
+msgid "Connected (encrypted) to "
+msgstr "Připojení (šifrované) k "
+
+#: ../app/ui.js:1081
+msgid "Connected (unencrypted) to "
+msgstr "Připojení (nešifrované) k "
+
+#: ../app/ui.js:1104
+msgid "Something went wrong, connection is closed"
+msgstr "Něco se pokazilo, odpojeno"
+
+#: ../app/ui.js:1107
+msgid "Failed to connect to server"
+msgstr "Chyba připojení k serveru"
+
+#: ../app/ui.js:1117
+msgid "Disconnected"
+msgstr "Odpojeno"
+
+#: ../app/ui.js:1130
+msgid "New connection has been rejected with reason: "
+msgstr "Nové připojení bylo odmítnuto s odůvodněním: "
+
+#: ../app/ui.js:1133
+msgid "New connection has been rejected"
+msgstr "Nové připojení bylo odmítnuto"
+
+#: ../app/ui.js:1153
+msgid "Password is required"
+msgstr "Je vyžadováno heslo"
+
+#: ../vnc.html:84
+msgid "noVNC encountered an error:"
+msgstr "noVNC narazilo na chybu:"
+
+#: ../vnc.html:94
+msgid "Hide/Show the control bar"
+msgstr "Skrýt/zobrazit ovládací panel"
+
+#: ../vnc.html:101
+msgid "Move/Drag Viewport"
+msgstr "Přesunout/přetáhnout výřez"
+
+#: ../vnc.html:101
+msgid "viewport drag"
+msgstr "přesun výřezu"
+
+#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
+msgid "Active Mouse Button"
+msgstr "Aktivní tlačítka myši"
+
+#: ../vnc.html:107
+msgid "No mousebutton"
+msgstr "Žádné"
+
+#: ../vnc.html:110
+msgid "Left mousebutton"
+msgstr "Levé tlačítko myši"
+
+#: ../vnc.html:113
+msgid "Middle mousebutton"
+msgstr "Prostřední tlačítko myši"
+
+#: ../vnc.html:116
+msgid "Right mousebutton"
+msgstr "Pravé tlačítko myši"
+
+#: ../vnc.html:119
+msgid "Keyboard"
+msgstr "Klávesnice"
+
+#: ../vnc.html:119
+msgid "Show Keyboard"
+msgstr "Zobrazit klávesnici"
+
+#: ../vnc.html:126
+msgid "Extra keys"
+msgstr "Extra klávesy"
+
+#: ../vnc.html:126
+msgid "Show Extra Keys"
+msgstr "Zobrazit extra klávesy"
+
+#: ../vnc.html:131
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:131
+msgid "Toggle Ctrl"
+msgstr "Přepnout Ctrl"
+
+#: ../vnc.html:134
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:134
+msgid "Toggle Alt"
+msgstr "Přepnout Alt"
+
+#: ../vnc.html:137
+msgid "Send Tab"
+msgstr "Odeslat tabulátor"
+
+#: ../vnc.html:137
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:140
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:140
+msgid "Send Escape"
+msgstr "Odeslat Esc"
+
+#: ../vnc.html:143
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:143
+msgid "Send Ctrl-Alt-Del"
+msgstr "Poslat Ctrl-Alt-Del"
+
+#: ../vnc.html:151
+msgid "Shutdown/Reboot"
+msgstr "Vypnutí/Restart"
+
+#: ../vnc.html:151
+msgid "Shutdown/Reboot..."
+msgstr "Vypnutí/Restart..."
+
+#: ../vnc.html:157
+msgid "Power"
+msgstr "Napájení"
+
+#: ../vnc.html:159
+msgid "Shutdown"
+msgstr "Vypnout"
+
+#: ../vnc.html:160
+msgid "Reboot"
+msgstr "Restart"
+
+#: ../vnc.html:161
+msgid "Reset"
+msgstr "Reset"
+
+#: ../vnc.html:166 ../vnc.html:172
+msgid "Clipboard"
+msgstr "Schránka"
+
+#: ../vnc.html:176
+msgid "Clear"
+msgstr "Vymazat"
+
+#: ../vnc.html:182
+msgid "Fullscreen"
+msgstr "Celá obrazovka"
+
+#: ../vnc.html:187 ../vnc.html:194
+msgid "Settings"
+msgstr "Nastavení"
+
+#: ../vnc.html:197
+msgid "Shared Mode"
+msgstr "Sdílený režim"
+
+#: ../vnc.html:200
+msgid "View Only"
+msgstr "Pouze prohlížení"
+
+#: ../vnc.html:204
+msgid "Clip to Window"
+msgstr "Přizpůsobit oknu"
+
+#: ../vnc.html:207
+msgid "Scaling Mode:"
+msgstr "Přizpůsobení velikosti"
+
+#: ../vnc.html:209
+msgid "None"
+msgstr "Žádné"
+
+#: ../vnc.html:210
+msgid "Local Scaling"
+msgstr "Místní"
+
+#: ../vnc.html:211
+msgid "Remote Resizing"
+msgstr "Vzdálené"
+
+#: ../vnc.html:216
+msgid "Advanced"
+msgstr "Pokročilé"
+
+#: ../vnc.html:219
+msgid "Repeater ID:"
+msgstr "ID opakovače"
+
+#: ../vnc.html:223
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:226
+msgid "Encrypt"
+msgstr "Šifrování:"
+
+#: ../vnc.html:229
+msgid "Host:"
+msgstr "Hostitel:"
+
+#: ../vnc.html:233
+msgid "Port:"
+msgstr "Port:"
+
+#: ../vnc.html:237
+msgid "Path:"
+msgstr "Cesta"
+
+#: ../vnc.html:244
+msgid "Automatic Reconnect"
+msgstr "Automatická obnova připojení"
+
+#: ../vnc.html:247
+msgid "Reconnect Delay (ms):"
+msgstr "Zpoždění připojení (ms)"
+
+#: ../vnc.html:252
+msgid "Show Dot when No Cursor"
+msgstr "Tečka místo chybějícího kurzoru myši"
+
+#: ../vnc.html:257
+msgid "Logging:"
+msgstr "Logování:"
+
+#: ../vnc.html:269
+msgid "Disconnect"
+msgstr "Odpojit"
+
+#: ../vnc.html:288
+msgid "Connect"
+msgstr "Připojit"
+
+#: ../vnc.html:298
+msgid "Password:"
+msgstr "Heslo"
+
+#: ../vnc.html:302
+msgid "Send Password"
+msgstr "Odeslat heslo"
+
+#: ../vnc.html:312
+msgid "Cancel"
+msgstr "Zrušit"
diff --git a/systemvm/agent/noVNC/po/de.po b/systemvm/agent/noVNC/po/de.po
new file mode 100644
index 0000000..0c3fa0d
--- /dev/null
+++ b/systemvm/agent/noVNC/po/de.po
@@ -0,0 +1,303 @@
+# German translations for noVNC package
+# German translation for noVNC.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Loek Janssen <loekjanssen@gmail.com>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2017-11-24 07:16+0000\n"
+"PO-Revision-Date: 2017-11-24 08:20+0100\n"
+"Last-Translator: Dominik Csapak <d.csapak@proxmox.com>\n"
+"Language-Team: none\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 1.8.11\n"
+
+#: ../app/ui.js:404
+msgid "Connecting..."
+msgstr "Verbinden..."
+
+#: ../app/ui.js:411
+msgid "Disconnecting..."
+msgstr "Verbindung trennen..."
+
+#: ../app/ui.js:417
+msgid "Reconnecting..."
+msgstr "Verbindung wiederherstellen..."
+
+#: ../app/ui.js:422
+msgid "Internal error"
+msgstr "Interner Fehler"
+
+#: ../app/ui.js:1019
+msgid "Must set host"
+msgstr "Richten Sie den Server ein"
+
+#: ../app/ui.js:1099
+msgid "Connected (encrypted) to "
+msgstr "Verbunden mit (verschlüsselt) "
+
+#: ../app/ui.js:1101
+msgid "Connected (unencrypted) to "
+msgstr "Verbunden mit (unverschlüsselt) "
+
+#: ../app/ui.js:1119
+msgid "Something went wrong, connection is closed"
+msgstr "Etwas lief schief, Verbindung wurde getrennt"
+
+#: ../app/ui.js:1129
+msgid "Disconnected"
+msgstr "Verbindung zum Server getrennt"
+
+#: ../app/ui.js:1142
+msgid "New connection has been rejected with reason: "
+msgstr "Verbindung wurde aus folgendem Grund abgelehnt: "
+
+#: ../app/ui.js:1145
+msgid "New connection has been rejected"
+msgstr "Verbindung wurde abgelehnt"
+
+#: ../app/ui.js:1166
+msgid "Password is required"
+msgstr "Passwort ist erforderlich"
+
+#: ../vnc.html:89
+msgid "noVNC encountered an error:"
+msgstr "Ein Fehler ist aufgetreten:"
+
+#: ../vnc.html:99
+msgid "Hide/Show the control bar"
+msgstr "Kontrollleiste verstecken/anzeigen"
+
+#: ../vnc.html:106
+msgid "Move/Drag Viewport"
+msgstr "Ansichtsfenster verschieben/ziehen"
+
+#: ../vnc.html:106
+msgid "viewport drag"
+msgstr "Ansichtsfenster ziehen"
+
+#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
+msgid "Active Mouse Button"
+msgstr "Aktive Maustaste"
+
+#: ../vnc.html:112
+msgid "No mousebutton"
+msgstr "Keine Maustaste"
+
+#: ../vnc.html:115
+msgid "Left mousebutton"
+msgstr "Linke Maustaste"
+
+#: ../vnc.html:118
+msgid "Middle mousebutton"
+msgstr "Mittlere Maustaste"
+
+#: ../vnc.html:121
+msgid "Right mousebutton"
+msgstr "Rechte Maustaste"
+
+#: ../vnc.html:124
+msgid "Keyboard"
+msgstr "Tastatur"
+
+#: ../vnc.html:124
+msgid "Show Keyboard"
+msgstr "Tastatur anzeigen"
+
+#: ../vnc.html:131
+msgid "Extra keys"
+msgstr "Zusatztasten"
+
+#: ../vnc.html:131
+msgid "Show Extra Keys"
+msgstr "Zusatztasten anzeigen"
+
+#: ../vnc.html:136
+msgid "Ctrl"
+msgstr "Strg"
+
+#: ../vnc.html:136
+msgid "Toggle Ctrl"
+msgstr "Strg umschalten"
+
+#: ../vnc.html:139
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:139
+msgid "Toggle Alt"
+msgstr "Alt umschalten"
+
+#: ../vnc.html:142
+msgid "Send Tab"
+msgstr "Tab senden"
+
+#: ../vnc.html:142
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:145
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:145
+msgid "Send Escape"
+msgstr "Escape senden"
+
+#: ../vnc.html:148
+msgid "Ctrl+Alt+Del"
+msgstr "Strg+Alt+Entf"
+
+#: ../vnc.html:148
+msgid "Send Ctrl-Alt-Del"
+msgstr "Strg+Alt+Entf senden"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot"
+msgstr "Herunterfahren/Neustarten"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot..."
+msgstr "Herunterfahren/Neustarten..."
+
+#: ../vnc.html:162
+msgid "Power"
+msgstr "Energie"
+
+#: ../vnc.html:164
+msgid "Shutdown"
+msgstr "Herunterfahren"
+
+#: ../vnc.html:165
+msgid "Reboot"
+msgstr "Neustarten"
+
+#: ../vnc.html:166
+msgid "Reset"
+msgstr "Zurücksetzen"
+
+#: ../vnc.html:171 ../vnc.html:177
+msgid "Clipboard"
+msgstr "Zwischenablage"
+
+#: ../vnc.html:181
+msgid "Clear"
+msgstr "Löschen"
+
+#: ../vnc.html:187
+msgid "Fullscreen"
+msgstr "Vollbild"
+
+#: ../vnc.html:192 ../vnc.html:199
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: ../vnc.html:202
+msgid "Shared Mode"
+msgstr "Geteilter Modus"
+
+#: ../vnc.html:205
+msgid "View Only"
+msgstr "Nur betrachten"
+
+#: ../vnc.html:209
+msgid "Clip to Window"
+msgstr "Auf Fenster begrenzen"
+
+#: ../vnc.html:212
+msgid "Scaling Mode:"
+msgstr "Skalierungsmodus:"
+
+#: ../vnc.html:214
+msgid "None"
+msgstr "Keiner"
+
+#: ../vnc.html:215
+msgid "Local Scaling"
+msgstr "Lokales skalieren"
+
+#: ../vnc.html:216
+msgid "Remote Resizing"
+msgstr "Serverseitiges skalieren"
+
+#: ../vnc.html:221
+msgid "Advanced"
+msgstr "Erweitert"
+
+#: ../vnc.html:224
+msgid "Repeater ID:"
+msgstr "Repeater ID:"
+
+#: ../vnc.html:228
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:231
+msgid "Encrypt"
+msgstr "Verschlüsselt"
+
+#: ../vnc.html:234
+msgid "Host:"
+msgstr "Server:"
+
+#: ../vnc.html:238
+msgid "Port:"
+msgstr "Port:"
+
+#: ../vnc.html:242
+msgid "Path:"
+msgstr "Pfad:"
+
+#: ../vnc.html:249
+msgid "Automatic Reconnect"
+msgstr "Automatisch wiederverbinden"
+
+#: ../vnc.html:252
+msgid "Reconnect Delay (ms):"
+msgstr "Wiederverbindungsverzögerung (ms):"
+
+#: ../vnc.html:258
+msgid "Logging:"
+msgstr "Protokollierung:"
+
+#: ../vnc.html:270
+msgid "Disconnect"
+msgstr "Verbindung trennen"
+
+#: ../vnc.html:289
+msgid "Connect"
+msgstr "Verbinden"
+
+#: ../vnc.html:299
+msgid "Password:"
+msgstr "Passwort:"
+
+#: ../vnc.html:313
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: ../vnc.html:329
+msgid "Canvas not supported."
+msgstr "Canvas nicht unterstützt."
+
+#~ msgid "Disconnect timeout"
+#~ msgstr "Zeitüberschreitung beim Trennen"
+
+#~ msgid "Local Downscaling"
+#~ msgstr "Lokales herunterskalieren"
+
+#~ msgid "Local Cursor"
+#~ msgstr "Lokaler Mauszeiger"
+
+#~ msgid "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen"
+#~ msgstr "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt"
+
+#~ msgid "True Color"
+#~ msgstr "True Color"
diff --git a/systemvm/agent/noVNC/po/el.po b/systemvm/agent/noVNC/po/el.po
new file mode 100644
index 0000000..5213ae5
--- /dev/null
+++ b/systemvm/agent/noVNC/po/el.po
@@ -0,0 +1,323 @@
+# Greek translations for noVNC package.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Giannis Kosmas <kosmasgiannis@gmail.com>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2017-11-17 21:40+0200\n"
+"PO-Revision-Date: 2017-10-11 16:16+0200\n"
+"Last-Translator: Giannis Kosmas <kosmasgiannis@gmail.com>\n"
+"Language-Team: none\n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../app/ui.js:404
+msgid "Connecting..."
+msgstr "Συνδέεται..."
+
+#: ../app/ui.js:411
+msgid "Disconnecting..."
+msgstr "Aποσυνδέεται..."
+
+#: ../app/ui.js:417
+msgid "Reconnecting..."
+msgstr "Επανασυνδέεται..."
+
+#: ../app/ui.js:422
+msgid "Internal error"
+msgstr "Εσωτερικό σφάλμα"
+
+#: ../app/ui.js:1019
+msgid "Must set host"
+msgstr "Πρέπει να οριστεί ο διακομιστής"
+
+#: ../app/ui.js:1099
+msgid "Connected (encrypted) to "
+msgstr "Συνδέθηκε (κρυπτογραφημένα) με το "
+
+#: ../app/ui.js:1101
+msgid "Connected (unencrypted) to "
+msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το "
+
+#: ../app/ui.js:1119
+msgid "Something went wrong, connection is closed"
+msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε"
+
+#: ../app/ui.js:1129
+msgid "Disconnected"
+msgstr "Αποσυνδέθηκε"
+
+#: ../app/ui.js:1142
+msgid "New connection has been rejected with reason: "
+msgstr "Η νέα σύνδεση απορρίφθηκε διότι: "
+
+#: ../app/ui.js:1145
+msgid "New connection has been rejected"
+msgstr "Η νέα σύνδεση απορρίφθηκε "
+
+#: ../app/ui.js:1166
+msgid "Password is required"
+msgstr "Απαιτείται ο κωδικός πρόσβασης"
+
+#: ../vnc.html:89
+msgid "noVNC encountered an error:"
+msgstr "το noVNC αντιμετώπισε ένα σφάλμα:"
+
+#: ../vnc.html:99
+msgid "Hide/Show the control bar"
+msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου"
+
+#: ../vnc.html:106
+msgid "Move/Drag Viewport"
+msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου"
+
+#: ../vnc.html:106
+msgid "viewport drag"
+msgstr "σύρσιμο θεατού πεδίου"
+
+#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
+msgid "Active Mouse Button"
+msgstr "Ενεργό Πλήκτρο Ποντικιού"
+
+#: ../vnc.html:112
+msgid "No mousebutton"
+msgstr "Χωρίς Πλήκτρο Ποντικιού"
+
+#: ../vnc.html:115
+msgid "Left mousebutton"
+msgstr "Αριστερό Πλήκτρο Ποντικιού"
+
+#: ../vnc.html:118
+msgid "Middle mousebutton"
+msgstr "Μεσαίο Πλήκτρο Ποντικιού"
+
+#: ../vnc.html:121
+msgid "Right mousebutton"
+msgstr "Δεξί Πλήκτρο Ποντικιού"
+
+#: ../vnc.html:124
+msgid "Keyboard"
+msgstr "Πληκτρολόγιο"
+
+#: ../vnc.html:124
+msgid "Show Keyboard"
+msgstr "Εμφάνιση Πληκτρολογίου"
+
+#: ../vnc.html:131
+msgid "Extra keys"
+msgstr "Επιπλέον πλήκτρα"
+
+#: ../vnc.html:131
+msgid "Show Extra Keys"
+msgstr "Εμφάνιση Επιπλέον Πλήκτρων"
+
+#: ../vnc.html:136
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:136
+msgid "Toggle Ctrl"
+msgstr "Εναλλαγή Ctrl"
+
+#: ../vnc.html:139
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:139
+msgid "Toggle Alt"
+msgstr "Εναλλαγή Alt"
+
+#: ../vnc.html:142
+msgid "Send Tab"
+msgstr "Αποστολή Tab"
+
+#: ../vnc.html:142
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:145
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:145
+msgid "Send Escape"
+msgstr "Αποστολή Escape"
+
+#: ../vnc.html:148
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:148
+msgid "Send Ctrl-Alt-Del"
+msgstr "Αποστολή Ctrl-Alt-Del"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot"
+msgstr "Κλείσιμο/Επανεκκίνηση"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot..."
+msgstr "Κλείσιμο/Επανεκκίνηση..."
+
+#: ../vnc.html:162
+msgid "Power"
+msgstr "Απενεργοποίηση"
+
+#: ../vnc.html:164
+msgid "Shutdown"
+msgstr "Κλείσιμο"
+
+#: ../vnc.html:165
+msgid "Reboot"
+msgstr "Επανεκκίνηση"
+
+#: ../vnc.html:166
+msgid "Reset"
+msgstr "Επαναφορά"
+
+#: ../vnc.html:171 ../vnc.html:177
+msgid "Clipboard"
+msgstr "Πρόχειρο"
+
+#: ../vnc.html:181
+msgid "Clear"
+msgstr "Καθάρισμα"
+
+#: ../vnc.html:187
+msgid "Fullscreen"
+msgstr "Πλήρης Οθόνη"
+
+#: ../vnc.html:192 ../vnc.html:199
+msgid "Settings"
+msgstr "Ρυθμίσεις"
+
+#: ../vnc.html:202
+msgid "Shared Mode"
+msgstr "Κοινόχρηστη Λειτουργία"
+
+#: ../vnc.html:205
+msgid "View Only"
+msgstr "Μόνο Θέαση"
+
+#: ../vnc.html:209
+msgid "Clip to Window"
+msgstr "Αποκοπή στο όριο του Παράθυρου"
+
+#: ../vnc.html:212
+msgid "Scaling Mode:"
+msgstr "Λειτουργία Κλιμάκωσης:"
+
+#: ../vnc.html:214
+msgid "None"
+msgstr "Καμία"
+
+#: ../vnc.html:215
+msgid "Local Scaling"
+msgstr "Τοπική Κλιμάκωση"
+
+#: ../vnc.html:216
+msgid "Remote Resizing"
+msgstr "Απομακρυσμένη Αλλαγή μεγέθους"
+
+#: ../vnc.html:221
+msgid "Advanced"
+msgstr "Για προχωρημένους"
+
+#: ../vnc.html:224
+msgid "Repeater ID:"
+msgstr "Repeater ID:"
+
+#: ../vnc.html:228
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:231
+msgid "Encrypt"
+msgstr "Κρυπτογράφηση"
+
+#: ../vnc.html:234
+msgid "Host:"
+msgstr "Όνομα διακομιστή:"
+
+#: ../vnc.html:238
+msgid "Port:"
+msgstr "Πόρτα διακομιστή:"
+
+#: ../vnc.html:242
+msgid "Path:"
+msgstr "Διαδρομή:"
+
+#: ../vnc.html:249
+msgid "Automatic Reconnect"
+msgstr "Αυτόματη επανασύνδεση"
+
+#: ../vnc.html:252
+msgid "Reconnect Delay (ms):"
+msgstr "Καθυστέρηση επανασύνδεσης (ms):"
+
+#: ../vnc.html:258
+msgid "Logging:"
+msgstr "Καταγραφή:"
+
+#: ../vnc.html:270
+msgid "Disconnect"
+msgstr "Αποσύνδεση"
+
+#: ../vnc.html:289
+msgid "Connect"
+msgstr "Σύνδεση"
+
+#: ../vnc.html:299
+msgid "Password:"
+msgstr "Κωδικός Πρόσβασης:"
+
+#: ../vnc.html:313
+msgid "Cancel"
+msgstr "Ακύρωση"
+
+#: ../vnc.html:329
+msgid "Canvas not supported."
+msgstr "Δεν υποστηρίζεται το στοιχείο Canvas"
+
+#~ msgid "Disconnect timeout"
+#~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης"
+
+#~ msgid "Local Downscaling"
+#~ msgstr "Τοπική Συρρίκνωση"
+
+#~ msgid "Local Cursor"
+#~ msgstr "Τοπικός Δρομέας"
+
+#~ msgid ""
+#~ "Forcing clipping mode since scrollbars aren't supported by IE in "
+#~ "fullscreen"
+#~ msgstr ""
+#~ "Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης "
+#~ "σε πλήρη οθόνη στον IE"
+
+#~ msgid "True Color"
+#~ msgstr "Πραγματικά Χρώματα"
+
+#~ msgid "Style:"
+#~ msgstr "Στυλ:"
+
+#~ msgid "default"
+#~ msgstr "προεπιλεγμένο"
+
+#~ msgid "Apply"
+#~ msgstr "Εφαρμογή"
+
+#~ msgid "Connection"
+#~ msgstr "Σύνδεση"
+
+#~ msgid "Token:"
+#~ msgstr "Διακριτικό:"
+
+#~ msgid "Send Password"
+#~ msgstr "Αποστολή Κωδικού Πρόσβασης"
diff --git a/systemvm/agent/noVNC/po/es.po b/systemvm/agent/noVNC/po/es.po
new file mode 100644
index 0000000..e15655f
--- /dev/null
+++ b/systemvm/agent/noVNC/po/es.po
@@ -0,0 +1,283 @@
+# Spanish translations for noVNC package
+# Traducciones al español para el paquete noVNC.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.0.0-testing.2\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2017-10-06 10:07+0200\n"
+"PO-Revision-Date: 2018-01-30 19:14-0800\n"
+"Last-Translator: Juanjo Diaz <juanjo.diazmo@gmail.com>\n"
+"Language-Team: Spanish\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../app/ui.js:430
+msgid "Connecting..."
+msgstr "Conectando..."
+
+#: ../app/ui.js:438
+msgid "Connected (encrypted) to "
+msgstr "Conectado (con encriptación) a"
+
+#: ../app/ui.js:440
+msgid "Connected (unencrypted) to "
+msgstr "Conectado (sin encriptación) a"
+
+#: ../app/ui.js:446
+msgid "Disconnecting..."
+msgstr "Desconectando..."
+
+#: ../app/ui.js:450
+msgid "Disconnected"
+msgstr "Desconectado"
+
+#: ../app/ui.js:1052 ../core/rfb.js:248
+msgid "Must set host"
+msgstr "Debes configurar el host"
+
+#: ../app/ui.js:1101
+msgid "Reconnecting..."
+msgstr "Reconectando..."
+
+#: ../app/ui.js:1140
+msgid "Password is required"
+msgstr "Contraseña es obligatoria"
+
+#: ../core/rfb.js:548
+msgid "Disconnect timeout"
+msgstr "Tiempo de desconexión agotado"
+
+#: ../vnc.html:89
+msgid "noVNC encountered an error:"
+msgstr "noVNC ha encontrado un error:"
+
+#: ../vnc.html:99
+msgid "Hide/Show the control bar"
+msgstr "Ocultar/Mostrar la barra de control"
+
+#: ../vnc.html:106
+msgid "Move/Drag Viewport"
+msgstr "Mover/Arrastrar la ventana"
+
+#: ../vnc.html:106
+msgid "viewport drag"
+msgstr "Arrastrar la ventana"
+
+#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
+msgid "Active Mouse Button"
+msgstr "Botón activo del ratón"
+
+#: ../vnc.html:112
+msgid "No mousebutton"
+msgstr "Ningún botón del ratón"
+
+#: ../vnc.html:115
+msgid "Left mousebutton"
+msgstr "Botón izquierdo del ratón"
+
+#: ../vnc.html:118
+msgid "Middle mousebutton"
+msgstr "Botón central del ratón"
+
+#: ../vnc.html:121
+msgid "Right mousebutton"
+msgstr "Botón derecho del ratón"
+
+#: ../vnc.html:124
+msgid "Keyboard"
+msgstr "Teclado"
+
+#: ../vnc.html:124
+msgid "Show Keyboard"
+msgstr "Mostrar teclado"
+
+#: ../vnc.html:131
+msgid "Extra keys"
+msgstr "Teclas adicionales"
+
+#: ../vnc.html:131
+msgid "Show Extra Keys"
+msgstr "Mostrar Teclas Adicionales"
+
+#: ../vnc.html:136
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:136
+msgid "Toggle Ctrl"
+msgstr "Pulsar/Soltar Ctrl"
+
+#: ../vnc.html:139
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:139
+msgid "Toggle Alt"
+msgstr "Pulsar/Soltar Alt"
+
+#: ../vnc.html:142
+msgid "Send Tab"
+msgstr "Enviar Tabulación"
+
+#: ../vnc.html:142
+msgid "Tab"
+msgstr "Tabulación"
+
+#: ../vnc.html:145
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:145
+msgid "Send Escape"
+msgstr "Enviar Escape"
+
+#: ../vnc.html:148
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:148
+msgid "Send Ctrl-Alt-Del"
+msgstr "Enviar Ctrl+Alt+Del"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot"
+msgstr "Apagar/Reiniciar"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot..."
+msgstr "Apagar/Reiniciar..."
+
+#: ../vnc.html:162
+msgid "Power"
+msgstr "Encender"
+
+#: ../vnc.html:164
+msgid "Shutdown"
+msgstr "Apagar"
+
+#: ../vnc.html:165
+msgid "Reboot"
+msgstr "Reiniciar"
+
+#: ../vnc.html:166
+msgid "Reset"
+msgstr "Restablecer"
+
+#: ../vnc.html:171 ../vnc.html:177
+msgid "Clipboard"
+msgstr "Portapapeles"
+
+#: ../vnc.html:181
+msgid "Clear"
+msgstr "Vaciar"
+
+#: ../vnc.html:187
+msgid "Fullscreen"
+msgstr "Pantalla Completa"
+
+#: ../vnc.html:192 ../vnc.html:199
+msgid "Settings"
+msgstr "Configuraciones"
+
+#: ../vnc.html:202
+msgid "Shared Mode"
+msgstr "Modo Compartido"
+
+#: ../vnc.html:205
+msgid "View Only"
+msgstr "Solo visualización"
+
+#: ../vnc.html:209
+msgid "Clip to Window"
+msgstr "Recortar al tamaño de la ventana"
+
+#: ../vnc.html:212
+msgid "Scaling Mode:"
+msgstr "Modo de escalado:"
+
+#: ../vnc.html:214
+msgid "None"
+msgstr "Ninguno"
+
+#: ../vnc.html:215
+msgid "Local Scaling"
+msgstr "Escalado Local"
+
+#: ../vnc.html:216
+msgid "Local Downscaling"
+msgstr "Reducción de escala local"
+
+#: ../vnc.html:217
+msgid "Remote Resizing"
+msgstr "Cambio de tamaño remoto"
+
+#: ../vnc.html:222
+msgid "Advanced"
+msgstr "Avanzado"
+
+#: ../vnc.html:225
+msgid "Local Cursor"
+msgstr "Cursor Local"
+
+#: ../vnc.html:229
+msgid "Repeater ID:"
+msgstr "ID del Repetidor"
+
+#: ../vnc.html:233
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:236
+msgid "Encrypt"
+msgstr ""
+
+#: ../vnc.html:239
+msgid "Host:"
+msgstr "Host"
+
+#: ../vnc.html:243
+msgid "Port:"
+msgstr "Puesto"
+
+#: ../vnc.html:247
+msgid "Path:"
+msgstr "Ruta"
+
+#: ../vnc.html:254
+msgid "Automatic Reconnect"
+msgstr "Reconexión automática"
+
+#: ../vnc.html:257
+msgid "Reconnect Delay (ms):"
+msgstr "Retraso en la reconexión (ms)"
+
+#: ../vnc.html:263
+msgid "Logging:"
+msgstr "Logging"
+
+#: ../vnc.html:275
+msgid "Disconnect"
+msgstr "Desconectar"
+
+#: ../vnc.html:294
+msgid "Connect"
+msgstr "Conectar"
+
+#: ../vnc.html:304
+msgid "Password:"
+msgstr "Contraseña"
+
+#: ../vnc.html:318
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../vnc.html:334
+msgid "Canvas not supported."
+msgstr "Canvas no está soportado"
diff --git a/systemvm/agent/noVNC/po/ko.po b/systemvm/agent/noVNC/po/ko.po
new file mode 100644
index 0000000..87ae106
--- /dev/null
+++ b/systemvm/agent/noVNC/po/ko.po
@@ -0,0 +1,290 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Baw Appie <pp121324@gmail.com>, 2018.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.0.0-testing.2\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2018-01-31 16:29+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Baw Appie <pp121324@gmail.com>\n"
+"Language-Team: Korean\n"
+"Language: ko\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../app/ui.js:395
+msgid "Connecting..."
+msgstr "연결중..."
+
+#: ../app/ui.js:402
+msgid "Disconnecting..."
+msgstr "연결 해제중..."
+
+#: ../app/ui.js:408
+msgid "Reconnecting..."
+msgstr "재연결중..."
+
+#: ../app/ui.js:413
+msgid "Internal error"
+msgstr "내부 오류"
+
+#: ../app/ui.js:1002
+msgid "Must set host"
+msgstr "호스트는 설정되어야 합니다."
+
+#: ../app/ui.js:1083
+msgid "Connected (encrypted) to "
+msgstr "다음과 (암호화되어) 연결되었습니다:"
+
+#: ../app/ui.js:1085
+msgid "Connected (unencrypted) to "
+msgstr "다음과 (암호화 없이) 연결되었습니다:"
+
+#: ../app/ui.js:1108
+msgid "Something went wrong, connection is closed"
+msgstr "무언가 잘못되었습니다, 연결이 닫혔습니다."
+
+#: ../app/ui.js:1111
+msgid "Failed to connect to server"
+msgstr "서버에 연결하지 못했습니다."
+
+#: ../app/ui.js:1121
+msgid "Disconnected"
+msgstr "연결이 해제되었습니다."
+
+#: ../app/ui.js:1134
+msgid "New connection has been rejected with reason: "
+msgstr "새 연결이 다음 이유로 거부되었습니다:"
+
+#: ../app/ui.js:1137
+msgid "New connection has been rejected"
+msgstr "새 연결이 거부되었습니다."
+
+#: ../app/ui.js:1158
+msgid "Password is required"
+msgstr "비밀번호가 필요합니다."
+
+#: ../vnc.html:91
+msgid "noVNC encountered an error:"
+msgstr "noVNC에 오류가 발생했습니다:"
+
+#: ../vnc.html:101
+msgid "Hide/Show the control bar"
+msgstr "컨트롤 바 숨기기/보이기"
+
+#: ../vnc.html:108
+msgid "Move/Drag Viewport"
+msgstr "움직이기/드래그 뷰포트"
+
+#: ../vnc.html:108
+msgid "viewport drag"
+msgstr "뷰포트 드래그"
+
+#: ../vnc.html:114 ../vnc.html:117 ../vnc.html:120 ../vnc.html:123
+msgid "Active Mouse Button"
+msgstr "마우스 버튼 활성화"
+
+#: ../vnc.html:114
+msgid "No mousebutton"
+msgstr "마우스 버튼 없음"
+
+#: ../vnc.html:117
+msgid "Left mousebutton"
+msgstr "왼쪽 마우스 버튼"
+
+#: ../vnc.html:120
+msgid "Middle mousebutton"
+msgstr "중간 마우스 버튼"
+
+#: ../vnc.html:123
+msgid "Right mousebutton"
+msgstr "오른쪽 마우스 버튼"
+
+#: ../vnc.html:126
+msgid "Keyboard"
+msgstr "키보드"
+
+#: ../vnc.html:126
+msgid "Show Keyboard"
+msgstr "키보드 보이기"
+
+#: ../vnc.html:133
+msgid "Extra keys"
+msgstr "기타 키들"
+
+#: ../vnc.html:133
+msgid "Show Extra Keys"
+msgstr "기타 키들 보이기"
+
+#: ../vnc.html:138
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:138
+msgid "Toggle Ctrl"
+msgstr "Ctrl 켜기/끄기"
+
+#: ../vnc.html:141
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:141
+msgid "Toggle Alt"
+msgstr "Alt 켜기/끄기"
+
+#: ../vnc.html:144
+msgid "Send Tab"
+msgstr "Tab 보내기"
+
+#: ../vnc.html:144
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:147
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:147
+msgid "Send Escape"
+msgstr "Esc 보내기"
+
+#: ../vnc.html:150
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:150
+msgid "Send Ctrl-Alt-Del"
+msgstr "Ctrl+Alt+Del 보내기"
+
+#: ../vnc.html:158
+msgid "Shutdown/Reboot"
+msgstr "셧다운/리붓"
+
+#: ../vnc.html:158
+msgid "Shutdown/Reboot..."
+msgstr "셧다운/리붓..."
+
+#: ../vnc.html:164
+msgid "Power"
+msgstr "전원"
+
+#: ../vnc.html:166
+msgid "Shutdown"
+msgstr "셧다운"
+
+#: ../vnc.html:167
+msgid "Reboot"
+msgstr "리붓"
+
+#: ../vnc.html:168
+msgid "Reset"
+msgstr "리셋"
+
+#: ../vnc.html:173 ../vnc.html:179
+msgid "Clipboard"
+msgstr "클립보드"
+
+#: ../vnc.html:183
+msgid "Clear"
+msgstr "지우기"
+
+#: ../vnc.html:189
+msgid "Fullscreen"
+msgstr "전체화면"
+
+#: ../vnc.html:194 ../vnc.html:201
+msgid "Settings"
+msgstr "설정"
+
+#: ../vnc.html:204
+msgid "Shared Mode"
+msgstr "공유 모드"
+
+#: ../vnc.html:207
+msgid "View Only"
+msgstr "보기 전용"
+
+#: ../vnc.html:211
+msgid "Clip to Window"
+msgstr "창에 클립"
+
+#: ../vnc.html:214
+msgid "Scaling Mode:"
+msgstr "스케일링 모드:"
+
+#: ../vnc.html:216
+msgid "None"
+msgstr "없음"
+
+#: ../vnc.html:217
+msgid "Local Scaling"
+msgstr "로컬 스케일링"
+
+#: ../vnc.html:218
+msgid "Remote Resizing"
+msgstr "원격 크기 조절"
+
+#: ../vnc.html:223
+msgid "Advanced"
+msgstr "고급"
+
+#: ../vnc.html:226
+msgid "Repeater ID:"
+msgstr "중계 ID"
+
+#: ../vnc.html:230
+msgid "WebSocket"
+msgstr "웹소켓"
+
+#: ../vnc.html:233
+msgid "Encrypt"
+msgstr "암호화"
+
+#: ../vnc.html:236
+msgid "Host:"
+msgstr "호스트:"
+
+#: ../vnc.html:240
+msgid "Port:"
+msgstr "포트:"
+
+#: ../vnc.html:244
+msgid "Path:"
+msgstr "위치:"
+
+#: ../vnc.html:251
+msgid "Automatic Reconnect"
+msgstr "자동 재연결"
+
+#: ../vnc.html:254
+msgid "Reconnect Delay (ms):"
+msgstr "재연결 지연 시간 (ms)"
+
+#: ../vnc.html:260
+msgid "Logging:"
+msgstr "로깅"
+
+#: ../vnc.html:272
+msgid "Disconnect"
+msgstr "연결 해제"
+
+#: ../vnc.html:291
+msgid "Connect"
+msgstr "연결"
+
+#: ../vnc.html:301
+msgid "Password:"
+msgstr "비밀번호:"
+
+#: ../vnc.html:305
+msgid "Send Password"
+msgstr "비밀번호 전송"
+
+#: ../vnc.html:315
+msgid "Cancel"
+msgstr "취소"
diff --git a/systemvm/agent/noVNC/po/nl.po b/systemvm/agent/noVNC/po/nl.po
new file mode 100644
index 0000000..343204a
--- /dev/null
+++ b/systemvm/agent/noVNC/po/nl.po
@@ -0,0 +1,322 @@
+# Dutch translations for noVNC package
+# Nederlandse vertalingen voor het pakket noVNC.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Loek Janssen <loekjanssen@gmail.com>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.1.0\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2019-04-09 11:06+0100\n"
+"PO-Revision-Date: 2019-04-09 17:17+0100\n"
+"Last-Translator: Arend Lapere <arend.lapere@gmail.com>\n"
+"Language-Team: none\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../app/ui.js:383
+msgid "Connecting..."
+msgstr "Verbinden..."
+
+#: ../app/ui.js:390
+msgid "Disconnecting..."
+msgstr "Verbinding verbreken..."
+
+#: ../app/ui.js:396
+msgid "Reconnecting..."
+msgstr "Opnieuw verbinding maken..."
+
+#: ../app/ui.js:401
+msgid "Internal error"
+msgstr "Interne fout"
+
+#: ../app/ui.js:991
+msgid "Must set host"
+msgstr "Host moeten worden ingesteld"
+
+#: ../app/ui.js:1073
+msgid "Connected (encrypted) to "
+msgstr "Verbonden (versleuteld) met "
+
+#: ../app/ui.js:1075
+msgid "Connected (unencrypted) to "
+msgstr "Verbonden (onversleuteld) met "
+
+#: ../app/ui.js:1098
+msgid "Something went wrong, connection is closed"
+msgstr "Er iets fout gelopen, verbinding werd verbroken"
+
+#: ../app/ui.js:1101
+msgid "Failed to connect to server"
+msgstr "Verbinding maken met server is mislukt"
+
+#: ../app/ui.js:1111
+msgid "Disconnected"
+msgstr "Verbinding verbroken"
+
+#: ../app/ui.js:1124
+msgid "New connection has been rejected with reason: "
+msgstr "Nieuwe verbinding is geweigerd omwille van de volgende reden: "
+
+#: ../app/ui.js:1127
+msgid "New connection has been rejected"
+msgstr "Nieuwe verbinding is geweigerd"
+
+#: ../app/ui.js:1147
+msgid "Password is required"
+msgstr "Wachtwoord is vereist"
+
+#: ../vnc.html:80
+msgid "noVNC encountered an error:"
+msgstr "noVNC heeft een fout bemerkt:"
+
+#: ../vnc.html:90
+msgid "Hide/Show the control bar"
+msgstr "Verberg/Toon de bedieningsbalk"
+
+#: ../vnc.html:97
+msgid "Move/Drag Viewport"
+msgstr "Verplaats/Versleep Kijkvenster"
+
+#: ../vnc.html:97
+msgid "viewport drag"
+msgstr "kijkvenster slepen"
+
+#: ../vnc.html:103 ../vnc.html:106 ../vnc.html:109 ../vnc.html:112
+msgid "Active Mouse Button"
+msgstr "Actieve Muisknop"
+
+#: ../vnc.html:103
+msgid "No mousebutton"
+msgstr "Geen muisknop"
+
+#: ../vnc.html:106
+msgid "Left mousebutton"
+msgstr "Linker muisknop"
+
+#: ../vnc.html:109
+msgid "Middle mousebutton"
+msgstr "Middelste muisknop"
+
+#: ../vnc.html:112
+msgid "Right mousebutton"
+msgstr "Rechter muisknop"
+
+#: ../vnc.html:115
+msgid "Keyboard"
+msgstr "Toetsenbord"
+
+#: ../vnc.html:115
+msgid "Show Keyboard"
+msgstr "Toon Toetsenbord"
+
+#: ../vnc.html:121
+msgid "Extra keys"
+msgstr "Extra toetsen"
+
+#: ../vnc.html:121
+msgid "Show Extra Keys"
+msgstr "Toon Extra Toetsen"
+
+#: ../vnc.html:126
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:126
+msgid "Toggle Ctrl"
+msgstr "Ctrl omschakelen"
+
+#: ../vnc.html:129
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:129
+msgid "Toggle Alt"
+msgstr "Alt omschakelen"
+
+#: ../vnc.html:132
+msgid "Toggle Windows"
+msgstr "Windows omschakelen"
+
+#: ../vnc.html:132
+msgid "Windows"
+msgstr "Windows"
+
+#: ../vnc.html:135
+msgid "Send Tab"
+msgstr "Tab Sturen"
+
+#: ../vnc.html:135
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:138
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:138
+msgid "Send Escape"
+msgstr "Escape Sturen"
+
+#: ../vnc.html:141
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl-Alt-Del"
+
+#: ../vnc.html:141
+msgid "Send Ctrl-Alt-Del"
+msgstr "Ctrl-Alt-Del Sturen"
+
+#: ../vnc.html:149
+msgid "Shutdown/Reboot"
+msgstr "Uitschakelen/Herstarten"
+
+#: ../vnc.html:149
+msgid "Shutdown/Reboot..."
+msgstr "Uitschakelen/Herstarten..."
+
+#: ../vnc.html:155
+msgid "Power"
+msgstr "Systeem"
+
+#: ../vnc.html:157
+msgid "Shutdown"
+msgstr "Uitschakelen"
+
+#: ../vnc.html:158
+msgid "Reboot"
+msgstr "Herstarten"
+
+#: ../vnc.html:159
+msgid "Reset"
+msgstr "Resetten"
+
+#: ../vnc.html:164 ../vnc.html:170
+msgid "Clipboard"
+msgstr "Klembord"
+
+#: ../vnc.html:174
+msgid "Clear"
+msgstr "Wissen"
+
+#: ../vnc.html:180
+msgid "Fullscreen"
+msgstr "Volledig Scherm"
+
+#: ../vnc.html:185 ../vnc.html:192
+msgid "Settings"
+msgstr "Instellingen"
+
+#: ../vnc.html:195
+msgid "Shared Mode"
+msgstr "Gedeelde Modus"
+
+#: ../vnc.html:198
+msgid "View Only"
+msgstr "Alleen Kijken"
+
+#: ../vnc.html:202
+msgid "Clip to Window"
+msgstr "Randen buiten venster afsnijden"
+
+#: ../vnc.html:205
+msgid "Scaling Mode:"
+msgstr "Schaalmodus:"
+
+#: ../vnc.html:207
+msgid "None"
+msgstr "Geen"
+
+#: ../vnc.html:208
+msgid "Local Scaling"
+msgstr "Lokaal Schalen"
+
+#: ../vnc.html:209
+msgid "Remote Resizing"
+msgstr "Op Afstand Formaat Wijzigen"
+
+#: ../vnc.html:214
+msgid "Advanced"
+msgstr "Geavanceerd"
+
+#: ../vnc.html:217
+msgid "Repeater ID:"
+msgstr "Repeater ID:"
+
+#: ../vnc.html:221
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:224
+msgid "Encrypt"
+msgstr "Versleutelen"
+
+#: ../vnc.html:227
+msgid "Host:"
+msgstr "Host:"
+
+#: ../vnc.html:231
+msgid "Port:"
+msgstr "Poort:"
+
+#: ../vnc.html:235
+msgid "Path:"
+msgstr "Pad:"
+
+#: ../vnc.html:242
+msgid "Automatic Reconnect"
+msgstr "Automatisch Opnieuw Verbinden"
+
+#: ../vnc.html:245
+msgid "Reconnect Delay (ms):"
+msgstr "Vertraging voor Opnieuw Verbinden (ms):"
+
+#: ../vnc.html:250
+msgid "Show Dot when No Cursor"
+msgstr "Geef stip weer indien geen cursor"
+
+#: ../vnc.html:255
+msgid "Logging:"
+msgstr "Logmeldingen:"
+
+#: ../vnc.html:267
+msgid "Disconnect"
+msgstr "Verbinding verbreken"
+
+#: ../vnc.html:286
+msgid "Connect"
+msgstr "Verbinden"
+
+#: ../vnc.html:296
+msgid "Password:"
+msgstr "Wachtwoord:"
+
+#: ../vnc.html:300
+msgid "Send Password"
+msgstr "Verzend Wachtwoord:"
+
+#: ../vnc.html:310
+msgid "Cancel"
+msgstr "Annuleren"
+
+#~ msgid "Disconnect timeout"
+#~ msgstr "Timeout tijdens verbreken van verbinding"
+
+#~ msgid "Local Downscaling"
+#~ msgstr "Lokaal Neerschalen"
+
+#~ msgid "Local Cursor"
+#~ msgstr "Lokale Cursor"
+
+#~ msgid "Canvas not supported."
+#~ msgstr "Canvas wordt niet ondersteund."
+
+#~ msgid ""
+#~ "Forcing clipping mode since scrollbars aren't supported by IE in "
+#~ "fullscreen"
+#~ msgstr ""
+#~ "''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-"
+#~ "modus in IE niet worden ondersteund"
diff --git a/systemvm/agent/noVNC/po/noVNC.pot b/systemvm/agent/noVNC/po/noVNC.pot
new file mode 100644
index 0000000..200be01
--- /dev/null
+++ b/systemvm/agent/noVNC/po/noVNC.pot
@@ -0,0 +1,302 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.1.0\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2019-01-16 11:06+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../app/ui.js:387
+msgid "Connecting..."
+msgstr ""
+
+#: ../app/ui.js:394
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../app/ui.js:400
+msgid "Reconnecting..."
+msgstr ""
+
+#: ../app/ui.js:405
+msgid "Internal error"
+msgstr ""
+
+#: ../app/ui.js:995
+msgid "Must set host"
+msgstr ""
+
+#: ../app/ui.js:1077
+msgid "Connected (encrypted) to "
+msgstr ""
+
+#: ../app/ui.js:1079
+msgid "Connected (unencrypted) to "
+msgstr ""
+
+#: ../app/ui.js:1102
+msgid "Something went wrong, connection is closed"
+msgstr ""
+
+#: ../app/ui.js:1105
+msgid "Failed to connect to server"
+msgstr ""
+
+#: ../app/ui.js:1115
+msgid "Disconnected"
+msgstr ""
+
+#: ../app/ui.js:1128
+msgid "New connection has been rejected with reason: "
+msgstr ""
+
+#: ../app/ui.js:1131
+msgid "New connection has been rejected"
+msgstr ""
+
+#: ../app/ui.js:1151
+msgid "Password is required"
+msgstr ""
+
+#: ../vnc.html:84
+msgid "noVNC encountered an error:"
+msgstr ""
+
+#: ../vnc.html:94
+msgid "Hide/Show the control bar"
+msgstr ""
+
+#: ../vnc.html:101
+msgid "Move/Drag Viewport"
+msgstr ""
+
+#: ../vnc.html:101
+msgid "viewport drag"
+msgstr ""
+
+#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
+msgid "Active Mouse Button"
+msgstr ""
+
+#: ../vnc.html:107
+msgid "No mousebutton"
+msgstr ""
+
+#: ../vnc.html:110
+msgid "Left mousebutton"
+msgstr ""
+
+#: ../vnc.html:113
+msgid "Middle mousebutton"
+msgstr ""
+
+#: ../vnc.html:116
+msgid "Right mousebutton"
+msgstr ""
+
+#: ../vnc.html:119
+msgid "Keyboard"
+msgstr ""
+
+#: ../vnc.html:119
+msgid "Show Keyboard"
+msgstr ""
+
+#: ../vnc.html:126
+msgid "Extra keys"
+msgstr ""
+
+#: ../vnc.html:126
+msgid "Show Extra Keys"
+msgstr ""
+
+#: ../vnc.html:131
+msgid "Ctrl"
+msgstr ""
+
+#: ../vnc.html:131
+msgid "Toggle Ctrl"
+msgstr ""
+
+#: ../vnc.html:134
+msgid "Alt"
+msgstr ""
+
+#: ../vnc.html:134
+msgid "Toggle Alt"
+msgstr ""
+
+#: ../vnc.html:137
+msgid "Toggle Windows"
+msgstr ""
+
+#: ../vnc.html:137
+msgid "Windows"
+msgstr ""
+
+#: ../vnc.html:140
+msgid "Send Tab"
+msgstr ""
+
+#: ../vnc.html:140
+msgid "Tab"
+msgstr ""
+
+#: ../vnc.html:143
+msgid "Esc"
+msgstr ""
+
+#: ../vnc.html:143
+msgid "Send Escape"
+msgstr ""
+
+#: ../vnc.html:146
+msgid "Ctrl+Alt+Del"
+msgstr ""
+
+#: ../vnc.html:146
+msgid "Send Ctrl-Alt-Del"
+msgstr ""
+
+#: ../vnc.html:154
+msgid "Shutdown/Reboot"
+msgstr ""
+
+#: ../vnc.html:154
+msgid "Shutdown/Reboot..."
+msgstr ""
+
+#: ../vnc.html:160
+msgid "Power"
+msgstr ""
+
+#: ../vnc.html:162
+msgid "Shutdown"
+msgstr ""
+
+#: ../vnc.html:163
+msgid "Reboot"
+msgstr ""
+
+#: ../vnc.html:164
+msgid "Reset"
+msgstr ""
+
+#: ../vnc.html:169 ../vnc.html:175
+msgid "Clipboard"
+msgstr ""
+
+#: ../vnc.html:179
+msgid "Clear"
+msgstr ""
+
+#: ../vnc.html:185
+msgid "Fullscreen"
+msgstr ""
+
+#: ../vnc.html:190 ../vnc.html:197
+msgid "Settings"
+msgstr ""
+
+#: ../vnc.html:200
+msgid "Shared Mode"
+msgstr ""
+
+#: ../vnc.html:203
+msgid "View Only"
+msgstr ""
+
+#: ../vnc.html:207
+msgid "Clip to Window"
+msgstr ""
+
+#: ../vnc.html:210
+msgid "Scaling Mode:"
+msgstr ""
+
+#: ../vnc.html:212
+msgid "None"
+msgstr ""
+
+#: ../vnc.html:213
+msgid "Local Scaling"
+msgstr ""
+
+#: ../vnc.html:214
+msgid "Remote Resizing"
+msgstr ""
+
+#: ../vnc.html:219
+msgid "Advanced"
+msgstr ""
+
+#: ../vnc.html:222
+msgid "Repeater ID:"
+msgstr ""
+
+#: ../vnc.html:226
+msgid "WebSocket"
+msgstr ""
+
+#: ../vnc.html:229
+msgid "Encrypt"
+msgstr ""
+
+#: ../vnc.html:232
+msgid "Host:"
+msgstr ""
+
+#: ../vnc.html:236
+msgid "Port:"
+msgstr ""
+
+#: ../vnc.html:240
+msgid "Path:"
+msgstr ""
+
+#: ../vnc.html:247
+msgid "Automatic Reconnect"
+msgstr ""
+
+#: ../vnc.html:250
+msgid "Reconnect Delay (ms):"
+msgstr ""
+
+#: ../vnc.html:255
+msgid "Show Dot when No Cursor"
+msgstr ""
+
+#: ../vnc.html:260
+msgid "Logging:"
+msgstr ""
+
+#: ../vnc.html:272
+msgid "Disconnect"
+msgstr ""
+
+#: ../vnc.html:291
+msgid "Connect"
+msgstr ""
+
+#: ../vnc.html:301
+msgid "Password:"
+msgstr ""
+
+#: ../vnc.html:305
+msgid "Send Password"
+msgstr ""
+
+#: ../vnc.html:315
+msgid "Cancel"
+msgstr ""
diff --git a/systemvm/agent/noVNC/po/pl.po b/systemvm/agent/noVNC/po/pl.po
new file mode 100644
index 0000000..5acfdc4
--- /dev/null
+++ b/systemvm/agent/noVNC/po/pl.po
@@ -0,0 +1,325 @@
+# Polish translations for noVNC package.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Mariusz Jamro <mariusz.jamro@gmail.com>, 2017.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2017-11-21 19:53+0100\n"
+"PO-Revision-Date: 2017-11-21 19:54+0100\n"
+"Last-Translator: Mariusz Jamro <mariusz.jamro@gmail.com>\n"
+"Language-Team: Polish\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 2.0.1\n"
+
+#: ../app/ui.js:404
+msgid "Connecting..."
+msgstr "Łączenie..."
+
+#: ../app/ui.js:411
+msgid "Disconnecting..."
+msgstr "Rozłączanie..."
+
+#: ../app/ui.js:417
+msgid "Reconnecting..."
+msgstr "Łączenie..."
+
+#: ../app/ui.js:422
+msgid "Internal error"
+msgstr "Błąd wewnętrzny"
+
+#: ../app/ui.js:1019
+msgid "Must set host"
+msgstr "Host i port są wymagane"
+
+#: ../app/ui.js:1099
+msgid "Connected (encrypted) to "
+msgstr "Połączenie (szyfrowane) z "
+
+#: ../app/ui.js:1101
+msgid "Connected (unencrypted) to "
+msgstr "Połączenie (nieszyfrowane) z "
+
+#: ../app/ui.js:1119
+msgid "Something went wrong, connection is closed"
+msgstr "Coś poszło źle, połączenie zostało zamknięte"
+
+#: ../app/ui.js:1129
+msgid "Disconnected"
+msgstr "Rozłączony"
+
+#: ../app/ui.js:1142
+msgid "New connection has been rejected with reason: "
+msgstr "Nowe połączenie zostało odrzucone z powodu: "
+
+#: ../app/ui.js:1145
+msgid "New connection has been rejected"
+msgstr "Nowe połączenie zostało odrzucone"
+
+#: ../app/ui.js:1166
+msgid "Password is required"
+msgstr "Hasło jest wymagane"
+
+#: ../vnc.html:89
+msgid "noVNC encountered an error:"
+msgstr "noVNC napotkało błąd:"
+
+#: ../vnc.html:99
+msgid "Hide/Show the control bar"
+msgstr "Pokaż/Ukryj pasek ustawień"
+
+#: ../vnc.html:106
+msgid "Move/Drag Viewport"
+msgstr "Ruszaj/Przeciągaj Viewport"
+
+#: ../vnc.html:106
+msgid "viewport drag"
+msgstr "przeciągnij viewport"
+
+#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
+msgid "Active Mouse Button"
+msgstr "Aktywny Przycisk Myszy"
+
+#: ../vnc.html:112
+msgid "No mousebutton"
+msgstr "Brak przycisku myszy"
+
+#: ../vnc.html:115
+msgid "Left mousebutton"
+msgstr "Lewy przycisk myszy"
+
+#: ../vnc.html:118
+msgid "Middle mousebutton"
+msgstr "Środkowy przycisk myszy"
+
+#: ../vnc.html:121
+msgid "Right mousebutton"
+msgstr "Prawy przycisk myszy"
+
+#: ../vnc.html:124
+msgid "Keyboard"
+msgstr "Klawiatura"
+
+#: ../vnc.html:124
+msgid "Show Keyboard"
+msgstr "Pokaż klawiaturę"
+
+#: ../vnc.html:131
+msgid "Extra keys"
+msgstr "Przyciski dodatkowe"
+
+#: ../vnc.html:131
+msgid "Show Extra Keys"
+msgstr "Pokaż przyciski dodatkowe"
+
+#: ../vnc.html:136
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:136
+msgid "Toggle Ctrl"
+msgstr "Przełącz Ctrl"
+
+#: ../vnc.html:139
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:139
+msgid "Toggle Alt"
+msgstr "Przełącz Alt"
+
+#: ../vnc.html:142
+msgid "Send Tab"
+msgstr "Wyślij Tab"
+
+#: ../vnc.html:142
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:145
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:145
+msgid "Send Escape"
+msgstr "Wyślij Escape"
+
+#: ../vnc.html:148
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:148
+msgid "Send Ctrl-Alt-Del"
+msgstr "Wyślij Ctrl-Alt-Del"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot"
+msgstr "Wyłącz/Uruchom ponownie"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot..."
+msgstr "Wyłącz/Uruchom ponownie..."
+
+#: ../vnc.html:162
+msgid "Power"
+msgstr "Włączony"
+
+#: ../vnc.html:164
+msgid "Shutdown"
+msgstr "Wyłącz"
+
+#: ../vnc.html:165
+msgid "Reboot"
+msgstr "Uruchom ponownie"
+
+#: ../vnc.html:166
+msgid "Reset"
+msgstr "Resetuj"
+
+#: ../vnc.html:171 ../vnc.html:177
+msgid "Clipboard"
+msgstr "Schowek"
+
+#: ../vnc.html:181
+msgid "Clear"
+msgstr "Wyczyść"
+
+#: ../vnc.html:187
+msgid "Fullscreen"
+msgstr "Pełny ekran"
+
+#: ../vnc.html:192 ../vnc.html:199
+msgid "Settings"
+msgstr "Ustawienia"
+
+#: ../vnc.html:202
+msgid "Shared Mode"
+msgstr "Tryb Współdzielenia"
+
+#: ../vnc.html:205
+msgid "View Only"
+msgstr "Tylko Podgląd"
+
+#: ../vnc.html:209
+msgid "Clip to Window"
+msgstr "Przytnij do Okna"
+
+#: ../vnc.html:212
+msgid "Scaling Mode:"
+msgstr "Tryb Skalowania:"
+
+#: ../vnc.html:214
+msgid "None"
+msgstr "Brak"
+
+#: ../vnc.html:215
+msgid "Local Scaling"
+msgstr "Skalowanie lokalne"
+
+#: ../vnc.html:216
+msgid "Remote Resizing"
+msgstr "Skalowanie zdalne"
+
+#: ../vnc.html:221
+msgid "Advanced"
+msgstr "Zaawansowane"
+
+#: ../vnc.html:224
+msgid "Repeater ID:"
+msgstr "ID Repeatera:"
+
+#: ../vnc.html:228
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:231
+msgid "Encrypt"
+msgstr "Szyfrowanie"
+
+#: ../vnc.html:234
+msgid "Host:"
+msgstr "Host:"
+
+#: ../vnc.html:238
+msgid "Port:"
+msgstr "Port:"
+
+#: ../vnc.html:242
+msgid "Path:"
+msgstr "Ścieżka:"
+
+#: ../vnc.html:249
+msgid "Automatic Reconnect"
+msgstr "Automatycznie wznawiaj połączenie"
+
+#: ../vnc.html:252
+msgid "Reconnect Delay (ms):"
+msgstr "Opóźnienie wznawiania (ms):"
+
+#: ../vnc.html:258
+msgid "Logging:"
+msgstr "Poziom logowania:"
+
+#: ../vnc.html:270
+msgid "Disconnect"
+msgstr "Rozłącz"
+
+#: ../vnc.html:289
+msgid "Connect"
+msgstr "Połącz"
+
+#: ../vnc.html:299
+msgid "Password:"
+msgstr "Hasło:"
+
+#: ../vnc.html:313
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: ../vnc.html:329
+msgid "Canvas not supported."
+msgstr "Element Canvas nie jest wspierany."
+
+#~ msgid "Disconnect timeout"
+#~ msgstr "Timeout rozłączenia"
+
+#~ msgid "Local Downscaling"
+#~ msgstr "Downscaling lokalny"
+
+#~ msgid "Local Cursor"
+#~ msgstr "Lokalny kursor"
+
+#~ msgid ""
+#~ "Forcing clipping mode since scrollbars aren't supported by IE in "
+#~ "fullscreen"
+#~ msgstr ""
+#~ "Wymuszam clipping mode ponieważ paski przewijania nie są wspierane przez "
+#~ "IE w trybie pełnoekranowym"
+
+#~ msgid "True Color"
+#~ msgstr "True Color"
+
+#~ msgid "Style:"
+#~ msgstr "Styl:"
+
+#~ msgid "default"
+#~ msgstr "domyślny"
+
+#~ msgid "Apply"
+#~ msgstr "Zapisz"
+
+#~ msgid "Connection"
+#~ msgstr "Połączenie"
+
+#~ msgid "Token:"
+#~ msgstr "Token:"
+
+#~ msgid "Send Password"
+#~ msgstr "Wyślij Hasło"
diff --git a/systemvm/agent/noVNC/po/po2js b/systemvm/agent/noVNC/po/po2js
new file mode 100755
index 0000000..03c1490
--- /dev/null
+++ b/systemvm/agent/noVNC/po/po2js
@@ -0,0 +1,43 @@
+#!/usr/bin/env node
+/*
+ * ps2js: gettext .po to noVNC .js converter
+ * Copyright (C) 2018 The noVNC Authors
+ *
+ * This program is free software: 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+const getopt = require('node-getopt');
+const fs = require('fs');
+const po2json = require("po2json");
+
+const opt = getopt.create([
+ ['h' , 'help' , 'display this help'],
+]).bindHelp().parseSystem();
+
+if (opt.argv.length != 2) {
+ console.error("Incorrect number of arguments given");
+ process.exit(1);
+}
+
+const data = po2json.parseFileSync(opt.argv[0]);
+
+const bodyPart = Object.keys(data).filter((msgid) => msgid !== "").map((msgid) => {
+ if (msgid === "") return;
+ const msgstr = data[msgid][1];
+ return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr);
+}).join(",\n");
+
+const output = "{\n" + bodyPart + "\n}";
+
+fs.writeFileSync(opt.argv[1], output);
diff --git a/systemvm/agent/noVNC/po/ru.po b/systemvm/agent/noVNC/po/ru.po
new file mode 100644
index 0000000..fb5d087
--- /dev/null
+++ b/systemvm/agent/noVNC/po/ru.po
@@ -0,0 +1,306 @@
+# Russian translations for noVNC package
+# Русский перевод для пакета noVNC.
+# Copyright (C) 2019 Dmitriy Shweew
+# This file is distributed under the same license as the noVNC package.
+# Dmitriy Shweew <shweew@it-advisor.ru>, 2019.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.1.0\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2019-02-26 14:53+0400\n"
+"PO-Revision-Date: 2019-02-17 17:29+0400\n"
+"Last-Translator: Dmitriy Shweew <shweew@it-advisor.ru>\n"
+"Language-Team: Russian\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 2.2.1\n"
+"X-Poedit-Flags-xgettext: --add-comments\n"
+
+#: ../app/ui.js:387
+msgid "Connecting..."
+msgstr "Подключение..."
+
+#: ../app/ui.js:394
+msgid "Disconnecting..."
+msgstr "Отключение..."
+
+#: ../app/ui.js:400
+msgid "Reconnecting..."
+msgstr "Переподключение..."
+
+#: ../app/ui.js:405
+msgid "Internal error"
+msgstr "Внутренняя ошибка"
+
+#: ../app/ui.js:995
+msgid "Must set host"
+msgstr "Задайте имя сервера или IP"
+
+#: ../app/ui.js:1077
+msgid "Connected (encrypted) to "
+msgstr "Подключено (с шифрованием) к "
+
+#: ../app/ui.js:1079
+msgid "Connected (unencrypted) to "
+msgstr "Подключено (без шифрования) к "
+
+#: ../app/ui.js:1102
+msgid "Something went wrong, connection is closed"
+msgstr "Что-то пошло не так, подключение разорвано"
+
+#: ../app/ui.js:1105
+msgid "Failed to connect to server"
+msgstr "Ошибка подключения к серверу"
+
+#: ../app/ui.js:1115
+msgid "Disconnected"
+msgstr "Отключено"
+
+#: ../app/ui.js:1128
+msgid "New connection has been rejected with reason: "
+msgstr "Подключиться не удалось: "
+
+#: ../app/ui.js:1131
+msgid "New connection has been rejected"
+msgstr "Подключиться не удалось"
+
+#: ../app/ui.js:1151
+msgid "Password is required"
+msgstr "Требуется пароль"
+
+#: ../vnc.html:84
+msgid "noVNC encountered an error:"
+msgstr "Ошибка noVNC: "
+
+#: ../vnc.html:94
+msgid "Hide/Show the control bar"
+msgstr "Скрыть/Показать контрольную панель"
+
+#: ../vnc.html:101
+msgid "Move/Drag Viewport"
+msgstr "Переместить окно"
+
+#: ../vnc.html:101
+msgid "viewport drag"
+msgstr "Переместить окно"
+
+#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
+msgid "Active Mouse Button"
+msgstr "Активировать кнопки мыши"
+
+#: ../vnc.html:107
+msgid "No mousebutton"
+msgstr "Отключить кнопки мыши"
+
+#: ../vnc.html:110
+msgid "Left mousebutton"
+msgstr "Левая кнопка мыши"
+
+#: ../vnc.html:113
+msgid "Middle mousebutton"
+msgstr "Средняя кнопка мыши"
+
+#: ../vnc.html:116
+msgid "Right mousebutton"
+msgstr "Правая кнопка мыши"
+
+#: ../vnc.html:119
+msgid "Keyboard"
+msgstr "Клавиатура"
+
+#: ../vnc.html:119
+msgid "Show Keyboard"
+msgstr "Показать клавиатуру"
+
+#: ../vnc.html:126
+msgid "Extra keys"
+msgstr "Доп. кнопки"
+
+#: ../vnc.html:126
+msgid "Show Extra Keys"
+msgstr "Показать дополнительные кнопки"
+
+#: ../vnc.html:131
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:131
+msgid "Toggle Ctrl"
+msgstr "Передать нажатие Ctrl"
+
+#: ../vnc.html:134
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:134
+msgid "Toggle Alt"
+msgstr "Передать нажатие Alt"
+
+#: ../vnc.html:137
+msgid "Toggle Windows"
+msgstr "Переключение вкладок"
+
+#: ../vnc.html:137
+msgid "Windows"
+msgstr "Вкладка"
+
+#: ../vnc.html:140
+msgid "Send Tab"
+msgstr "Передать нажатие Tab"
+
+#: ../vnc.html:140
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:143
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:143
+msgid "Send Escape"
+msgstr "Передать нажатие Escape"
+
+#: ../vnc.html:146
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:146
+msgid "Send Ctrl-Alt-Del"
+msgstr "Передать нажатие Ctrl-Alt-Del"
+
+#: ../vnc.html:154
+msgid "Shutdown/Reboot"
+msgstr "Выключить/Перезагрузить"
+
+#: ../vnc.html:154
+msgid "Shutdown/Reboot..."
+msgstr "Выключить/Перезагрузить..."
+
+#: ../vnc.html:160
+msgid "Power"
+msgstr "Питание"
+
+#: ../vnc.html:162
+msgid "Shutdown"
+msgstr "Выключить"
+
+#: ../vnc.html:163
+msgid "Reboot"
+msgstr "Перезагрузить"
+
+#: ../vnc.html:164
+msgid "Reset"
+msgstr "Сброс"
+
+#: ../vnc.html:169 ../vnc.html:175
+msgid "Clipboard"
+msgstr "Буфер обмена"
+
+#: ../vnc.html:179
+msgid "Clear"
+msgstr "Очистить"
+
+#: ../vnc.html:185
+msgid "Fullscreen"
+msgstr "Во весь экран"
+
+#: ../vnc.html:190 ../vnc.html:197
+msgid "Settings"
+msgstr "Настройки"
+
+#: ../vnc.html:200
+msgid "Shared Mode"
+msgstr "Общий режим"
+
+#: ../vnc.html:203
+msgid "View Only"
+msgstr "Просмотр"
+
+#: ../vnc.html:207
+msgid "Clip to Window"
+msgstr "В окно"
+
+#: ../vnc.html:210
+msgid "Scaling Mode:"
+msgstr "Масштаб:"
+
+#: ../vnc.html:212
+msgid "None"
+msgstr "Нет"
+
+#: ../vnc.html:213
+msgid "Local Scaling"
+msgstr "Локльный масштаб"
+
+#: ../vnc.html:214
+msgid "Remote Resizing"
+msgstr "Удаленный масштаб"
+
+#: ../vnc.html:219
+msgid "Advanced"
+msgstr "Дополнительно"
+
+#: ../vnc.html:222
+msgid "Repeater ID:"
+msgstr "Идентификатор ID:"
+
+#: ../vnc.html:226
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:229
+msgid "Encrypt"
+msgstr "Шифрование"
+
+#: ../vnc.html:232
+msgid "Host:"
+msgstr "Сервер:"
+
+#: ../vnc.html:236
+msgid "Port:"
+msgstr "Порт:"
+
+#: ../vnc.html:240
+msgid "Path:"
+msgstr "Путь:"
+
+#: ../vnc.html:247
+msgid "Automatic Reconnect"
+msgstr "Автоматическое переподключение"
+
+#: ../vnc.html:250
+msgid "Reconnect Delay (ms):"
+msgstr "Задержка переподключения (мс):"
+
+#: ../vnc.html:255
+msgid "Show Dot when No Cursor"
+msgstr "Показать точку вместо курсора"
+
+#: ../vnc.html:260
+msgid "Logging:"
+msgstr "Лог:"
+
+#: ../vnc.html:272
+msgid "Disconnect"
+msgstr "Отключение"
+
+#: ../vnc.html:291
+msgid "Connect"
+msgstr "Подключение"
+
+#: ../vnc.html:301
+msgid "Password:"
+msgstr "Пароль:"
+
+#: ../vnc.html:305
+msgid "Send Password"
+msgstr "Пароль: "
+
+#: ../vnc.html:315
+msgid "Cancel"
+msgstr "Выход"
diff --git a/systemvm/agent/noVNC/po/sv.po b/systemvm/agent/noVNC/po/sv.po
new file mode 100644
index 0000000..f795566
--- /dev/null
+++ b/systemvm/agent/noVNC/po/sv.po
@@ -0,0 +1,316 @@
+# Swedish translations for noVNC package
+# Svenska översättningar för paket noVNC.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Samuel Mannehed <samuel@cendio.se>, 2019.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.1.0\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2019-01-16 11:06+0100\n"
+"PO-Revision-Date: 2019-04-08 10:18+0200\n"
+"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
+"Language-Team: none\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 2.0.3\n"
+
+#: ../app/ui.js:387
+msgid "Connecting..."
+msgstr "Ansluter..."
+
+#: ../app/ui.js:394
+msgid "Disconnecting..."
+msgstr "Kopplar ner..."
+
+#: ../app/ui.js:400
+msgid "Reconnecting..."
+msgstr "Återansluter..."
+
+#: ../app/ui.js:405
+msgid "Internal error"
+msgstr "Internt fel"
+
+#: ../app/ui.js:995
+msgid "Must set host"
+msgstr "Du måste specifiera en värd"
+
+#: ../app/ui.js:1077
+msgid "Connected (encrypted) to "
+msgstr "Ansluten (krypterat) till "
+
+#: ../app/ui.js:1079
+msgid "Connected (unencrypted) to "
+msgstr "Ansluten (okrypterat) till "
+
+#: ../app/ui.js:1102
+msgid "Something went wrong, connection is closed"
+msgstr "Något gick fel, anslutningen avslutades"
+
+#: ../app/ui.js:1105
+msgid "Failed to connect to server"
+msgstr "Misslyckades att ansluta till servern"
+
+#: ../app/ui.js:1115
+msgid "Disconnected"
+msgstr "Frånkopplad"
+
+#: ../app/ui.js:1128
+msgid "New connection has been rejected with reason: "
+msgstr "Ny anslutning har blivit nekad med följande skäl: "
+
+#: ../app/ui.js:1131
+msgid "New connection has been rejected"
+msgstr "Ny anslutning har blivit nekad"
+
+#: ../app/ui.js:1151
+msgid "Password is required"
+msgstr "Lösenord krävs"
+
+#: ../vnc.html:84
+msgid "noVNC encountered an error:"
+msgstr "noVNC stötte på ett problem:"
+
+#: ../vnc.html:94
+msgid "Hide/Show the control bar"
+msgstr "Göm/Visa kontrollbaren"
+
+#: ../vnc.html:101
+msgid "Move/Drag Viewport"
+msgstr "Flytta/Dra Vyn"
+
+#: ../vnc.html:101
+msgid "viewport drag"
+msgstr "dra vy"
+
+#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
+msgid "Active Mouse Button"
+msgstr "Aktiv musknapp"
+
+#: ../vnc.html:107
+msgid "No mousebutton"
+msgstr "Ingen musknapp"
+
+#: ../vnc.html:110
+msgid "Left mousebutton"
+msgstr "Vänster musknapp"
+
+#: ../vnc.html:113
+msgid "Middle mousebutton"
+msgstr "Mitten-musknapp"
+
+#: ../vnc.html:116
+msgid "Right mousebutton"
+msgstr "Höger musknapp"
+
+#: ../vnc.html:119
+msgid "Keyboard"
+msgstr "Tangentbord"
+
+#: ../vnc.html:119
+msgid "Show Keyboard"
+msgstr "Visa Tangentbord"
+
+#: ../vnc.html:126
+msgid "Extra keys"
+msgstr "Extraknappar"
+
+#: ../vnc.html:126
+msgid "Show Extra Keys"
+msgstr "Visa Extraknappar"
+
+#: ../vnc.html:131
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:131
+msgid "Toggle Ctrl"
+msgstr "Växla Ctrl"
+
+#: ../vnc.html:134
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:134
+msgid "Toggle Alt"
+msgstr "Växla Alt"
+
+#: ../vnc.html:137
+msgid "Toggle Windows"
+msgstr "Växla Windows"
+
+#: ../vnc.html:137
+msgid "Windows"
+msgstr "Windows"
+
+#: ../vnc.html:140
+msgid "Send Tab"
+msgstr "Skicka Tab"
+
+#: ../vnc.html:140
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:143
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:143
+msgid "Send Escape"
+msgstr "Skicka Escape"
+
+#: ../vnc.html:146
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:146
+msgid "Send Ctrl-Alt-Del"
+msgstr "Skicka Ctrl-Alt-Del"
+
+#: ../vnc.html:154
+msgid "Shutdown/Reboot"
+msgstr "Stäng av/Boota om"
+
+#: ../vnc.html:154
+msgid "Shutdown/Reboot..."
+msgstr "Stäng av/Boota om..."
+
+#: ../vnc.html:160
+msgid "Power"
+msgstr "Ström"
+
+#: ../vnc.html:162
+msgid "Shutdown"
+msgstr "Stäng av"
+
+#: ../vnc.html:163
+msgid "Reboot"
+msgstr "Boota om"
+
+#: ../vnc.html:164
+msgid "Reset"
+msgstr "Återställ"
+
+#: ../vnc.html:169 ../vnc.html:175
+msgid "Clipboard"
+msgstr "Urklipp"
+
+#: ../vnc.html:179
+msgid "Clear"
+msgstr "Rensa"
+
+#: ../vnc.html:185
+msgid "Fullscreen"
+msgstr "Fullskärm"
+
+#: ../vnc.html:190 ../vnc.html:197
+msgid "Settings"
+msgstr "Inställningar"
+
+#: ../vnc.html:200
+msgid "Shared Mode"
+msgstr "Delat Läge"
+
+#: ../vnc.html:203
+msgid "View Only"
+msgstr "Endast Visning"
+
+#: ../vnc.html:207
+msgid "Clip to Window"
+msgstr "Begränsa till Fönster"
+
+#: ../vnc.html:210
+msgid "Scaling Mode:"
+msgstr "Skalningsläge:"
+
+#: ../vnc.html:212
+msgid "None"
+msgstr "Ingen"
+
+#: ../vnc.html:213
+msgid "Local Scaling"
+msgstr "Lokal Skalning"
+
+#: ../vnc.html:214
+msgid "Remote Resizing"
+msgstr "Ändra Storlek"
+
+#: ../vnc.html:219
+msgid "Advanced"
+msgstr "Avancerat"
+
+#: ../vnc.html:222
+msgid "Repeater ID:"
+msgstr "Repeater-ID:"
+
+#: ../vnc.html:226
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:229
+msgid "Encrypt"
+msgstr "Kryptera"
+
+#: ../vnc.html:232
+msgid "Host:"
+msgstr "Värd:"
+
+#: ../vnc.html:236
+msgid "Port:"
+msgstr "Port:"
+
+#: ../vnc.html:240
+msgid "Path:"
+msgstr "Sökväg:"
+
+#: ../vnc.html:247
+msgid "Automatic Reconnect"
+msgstr "Automatisk Återanslutning"
+
+#: ../vnc.html:250
+msgid "Reconnect Delay (ms):"
+msgstr "Fördröjning (ms):"
+
+#: ../vnc.html:255
+msgid "Show Dot when No Cursor"
+msgstr "Visa prick när ingen muspekare finns"
+
+#: ../vnc.html:260
+msgid "Logging:"
+msgstr "Loggning:"
+
+#: ../vnc.html:272
+msgid "Disconnect"
+msgstr "Koppla från"
+
+#: ../vnc.html:291
+msgid "Connect"
+msgstr "Anslut"
+
+#: ../vnc.html:301
+msgid "Password:"
+msgstr "Lösenord:"
+
+#: ../vnc.html:305
+msgid "Send Password"
+msgstr "Skicka lösenord"
+
+#: ../vnc.html:315
+msgid "Cancel"
+msgstr "Avbryt"
+
+#~ msgid "Disconnect timeout"
+#~ msgstr "Det tog för lång tid att koppla ner"
+
+#~ msgid "Local Downscaling"
+#~ msgstr "Lokal Nedskalning"
+
+#~ msgid "Local Cursor"
+#~ msgstr "Lokal Muspekare"
+
+#~ msgid "Canvas not supported."
+#~ msgstr "Canvas stöds ej"
diff --git a/systemvm/agent/noVNC/po/tr.po b/systemvm/agent/noVNC/po/tr.po
new file mode 100644
index 0000000..8b5c181
--- /dev/null
+++ b/systemvm/agent/noVNC/po/tr.po
@@ -0,0 +1,288 @@
+# Turkish translations for noVNC package
+# Turkish translation for noVNC.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Ömer ÇAKMAK <farukomercakmak@gmail.com>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2017-11-24 07:16+0000\n"
+"PO-Revision-Date: 2018-01-05 19:07+0300\n"
+"Last-Translator: Ömer ÇAKMAK <farukomercakmak@gmail.com>\n"
+"Language-Team: Türkçe <gnome-turk@gnome.org>\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Gtranslator 2.91.7\n"
+
+#: ../app/ui.js:404
+msgid "Connecting..."
+msgstr "Bağlanıyor..."
+
+#: ../app/ui.js:411
+msgid "Disconnecting..."
+msgstr "Bağlantı kesiliyor..."
+
+#: ../app/ui.js:417
+msgid "Reconnecting..."
+msgstr "Yeniden bağlantı kuruluyor..."
+
+#: ../app/ui.js:422
+msgid "Internal error"
+msgstr "İç hata"
+
+#: ../app/ui.js:1019
+msgid "Must set host"
+msgstr "Sunucuyu kur"
+
+#: ../app/ui.js:1099
+msgid "Connected (encrypted) to "
+msgstr "Bağlı (şifrelenmiş)"
+
+#: ../app/ui.js:1101
+msgid "Connected (unencrypted) to "
+msgstr "Bağlandı (şifrelenmemiş)"
+
+#: ../app/ui.js:1119
+msgid "Something went wrong, connection is closed"
+msgstr "Bir şeyler ters gitti, bağlantı kesildi"
+
+#: ../app/ui.js:1129
+msgid "Disconnected"
+msgstr "Bağlantı kesildi"
+
+#: ../app/ui.js:1142
+msgid "New connection has been rejected with reason: "
+msgstr "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: "
+
+#: ../app/ui.js:1145
+msgid "New connection has been rejected"
+msgstr "Bağlantı reddedildi"
+
+#: ../app/ui.js:1166
+msgid "Password is required"
+msgstr "Şifre gerekli"
+
+#: ../vnc.html:89
+msgid "noVNC encountered an error:"
+msgstr "Bir hata oluştu:"
+
+#: ../vnc.html:99
+msgid "Hide/Show the control bar"
+msgstr "Denetim masasını Gizle/Göster"
+
+#: ../vnc.html:106
+msgid "Move/Drag Viewport"
+msgstr "Görünümü Taşı/Sürükle"
+
+#: ../vnc.html:106
+msgid "viewport drag"
+msgstr "Görüntü penceresini sürükle"
+
+#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
+msgid "Active Mouse Button"
+msgstr "Aktif Fare Düğmesi"
+
+#: ../vnc.html:112
+msgid "No mousebutton"
+msgstr "Fare düğmesi yok"
+
+#: ../vnc.html:115
+msgid "Left mousebutton"
+msgstr "Farenin sol düğmesi"
+
+#: ../vnc.html:118
+msgid "Middle mousebutton"
+msgstr "Farenin orta düğmesi"
+
+#: ../vnc.html:121
+msgid "Right mousebutton"
+msgstr "Farenin sağ düğmesi"
+
+#: ../vnc.html:124
+msgid "Keyboard"
+msgstr "Klavye"
+
+#: ../vnc.html:124
+msgid "Show Keyboard"
+msgstr "Klavye Düzenini Göster"
+
+#: ../vnc.html:131
+msgid "Extra keys"
+msgstr "Ekstra tuşlar"
+
+#: ../vnc.html:131
+msgid "Show Extra Keys"
+msgstr "Ekstra tuşları göster"
+
+#: ../vnc.html:136
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:136
+msgid "Toggle Ctrl"
+msgstr "Ctrl Değiştir "
+
+#: ../vnc.html:139
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:139
+msgid "Toggle Alt"
+msgstr "Alt Değiştir"
+
+#: ../vnc.html:142
+msgid "Send Tab"
+msgstr "Sekme Gönder"
+
+#: ../vnc.html:142
+msgid "Tab"
+msgstr "Sekme"
+
+#: ../vnc.html:145
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:145
+msgid "Send Escape"
+msgstr "Boşluk Gönder"
+
+#: ../vnc.html:148
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl + Alt + Del"
+
+#: ../vnc.html:148
+msgid "Send Ctrl-Alt-Del"
+msgstr "Ctrl-Alt-Del Gönder"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot"
+msgstr "Kapat/Yeniden Başlat"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot..."
+msgstr "Kapat/Yeniden Başlat..."
+
+#: ../vnc.html:162
+msgid "Power"
+msgstr "Güç"
+
+#: ../vnc.html:164
+msgid "Shutdown"
+msgstr "Kapat"
+
+#: ../vnc.html:165
+msgid "Reboot"
+msgstr "Yeniden Başlat"
+
+#: ../vnc.html:166
+msgid "Reset"
+msgstr "Sıfırla"
+
+#: ../vnc.html:171 ../vnc.html:177
+msgid "Clipboard"
+msgstr "Pano"
+
+#: ../vnc.html:181
+msgid "Clear"
+msgstr "Temizle"
+
+#: ../vnc.html:187
+msgid "Fullscreen"
+msgstr "Tam Ekran"
+
+#: ../vnc.html:192 ../vnc.html:199
+msgid "Settings"
+msgstr "Ayarlar"
+
+#: ../vnc.html:202
+msgid "Shared Mode"
+msgstr "Paylaşım Modu"
+
+#: ../vnc.html:205
+msgid "View Only"
+msgstr "Sadece Görüntüle"
+
+#: ../vnc.html:209
+msgid "Clip to Window"
+msgstr "Pencereye Tıkla"
+
+#: ../vnc.html:212
+msgid "Scaling Mode:"
+msgstr "Ölçekleme Modu:"
+
+#: ../vnc.html:214
+msgid "None"
+msgstr "Bilinmeyen"
+
+#: ../vnc.html:215
+msgid "Local Scaling"
+msgstr "Yerel Ölçeklendirme"
+
+#: ../vnc.html:216
+msgid "Remote Resizing"
+msgstr "Uzaktan Yeniden Boyutlandırma"
+
+#: ../vnc.html:221
+msgid "Advanced"
+msgstr "Gelişmiş"
+
+#: ../vnc.html:224
+msgid "Repeater ID:"
+msgstr "Tekralayıcı ID:"
+
+#: ../vnc.html:228
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:231
+msgid "Encrypt"
+msgstr "Şifrele"
+
+#: ../vnc.html:234
+msgid "Host:"
+msgstr "Ana makine:"
+
+#: ../vnc.html:238
+msgid "Port:"
+msgstr "Port:"
+
+#: ../vnc.html:242
+msgid "Path:"
+msgstr "Yol:"
+
+#: ../vnc.html:249
+msgid "Automatic Reconnect"
+msgstr "Otomatik Yeniden Bağlan"
+
+#: ../vnc.html:252
+msgid "Reconnect Delay (ms):"
+msgstr "Yeniden Bağlanma Süreci (ms):"
+
+#: ../vnc.html:258
+msgid "Logging:"
+msgstr "Giriş yapılıyor:"
+
+#: ../vnc.html:270
+msgid "Disconnect"
+msgstr "Bağlantıyı Kes"
+
+#: ../vnc.html:289
+msgid "Connect"
+msgstr "Bağlan"
+
+#: ../vnc.html:299
+msgid "Password:"
+msgstr "Parola:"
+
+#: ../vnc.html:313
+msgid "Cancel"
+msgstr "Vazgeç"
+
+#: ../vnc.html:329
+msgid "Canvas not supported."
+msgstr "Tuval desteklenmiyor."
diff --git a/systemvm/agent/noVNC/po/xgettext-html b/systemvm/agent/noVNC/po/xgettext-html
new file mode 100755
index 0000000..547f568
--- /dev/null
+++ b/systemvm/agent/noVNC/po/xgettext-html
@@ -0,0 +1,115 @@
+#!/usr/bin/env node
+/*
+ * xgettext-html: HTML gettext parser
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ */
+
+const getopt = require('node-getopt');
+const jsdom = require("jsdom");
+const fs = require("fs");
+
+const opt = getopt.create([
+ ['o' , 'output=FILE' , 'write output to specified file'],
+ ['h' , 'help' , 'display this help'],
+]).bindHelp().parseSystem();
+
+const strings = {};
+
+function addString(str, location) {
+ if (str.length == 0) {
+ return;
+ }
+
+ if (strings[str] === undefined) {
+ strings[str] = {}
+ }
+ strings[str][location] = null;
+}
+
+// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
+function process(elem, locator, enabled) {
+ function isAnyOf(searchElement, items) {
+ return items.indexOf(searchElement) !== -1;
+ }
+
+ if (elem.hasAttribute("translate")) {
+ if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
+ enabled = true;
+ } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
+ enabled = false;
+ }
+ }
+
+ if (enabled) {
+ if (elem.hasAttribute("abbr") &&
+ elem.tagName === "TH") {
+ addString(elem.getAttribute("abbr"), locator(elem));
+ }
+ if (elem.hasAttribute("alt") &&
+ isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
+ addString(elem.getAttribute("alt"), locator(elem));
+ }
+ if (elem.hasAttribute("download") &&
+ isAnyOf(elem.tagName, ["A", "AREA"])) {
+ addString(elem.getAttribute("download"), locator(elem));
+ }
+ if (elem.hasAttribute("label") &&
+ isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
+ "OPTION", "TRACK"])) {
+ addString(elem.getAttribute("label"), locator(elem));
+ }
+ if (elem.hasAttribute("placeholder") &&
+ isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) {
+ addString(elem.getAttribute("placeholder"), locator(elem));
+ }
+ if (elem.hasAttribute("title")) {
+ addString(elem.getAttribute("title"), locator(elem));
+ }
+ if (elem.hasAttribute("value") &&
+ elem.tagName === "INPUT" &&
+ isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) {
+ addString(elem.getAttribute("value"), locator(elem));
+ }
+ }
+
+ for (let i = 0; i < elem.childNodes.length; i++) {
+ node = elem.childNodes[i];
+ if (node.nodeType === node.ELEMENT_NODE) {
+ process(node, locator, enabled);
+ } else if (node.nodeType === node.TEXT_NODE && enabled) {
+ addString(node.data.trim(), locator(node));
+ }
+ }
+}
+
+for (let i = 0; i < opt.argv.length; i++) {
+ const fn = opt.argv[i];
+ const file = fs.readFileSync(fn, "utf8");
+ const dom = new jsdom.JSDOM(file, { includeNodeLocations: true });
+ const body = dom.window.document.body;
+
+ function locator(elem) {
+ const offset = dom.nodeLocation(elem).startOffset;
+ const line = file.slice(0, offset).split("\n").length;
+ return fn + ":" + line;
+ }
+
+ process(body, locator, true);
+}
+
+let output = "";
+
+for (str in strings) {
+ output += "#:";
+ for (location in strings[str]) {
+ output += " " + location;
+ }
+ output += "\n";
+
+ output += "msgid " + JSON.stringify(str) + "\n";
+ output += "msgstr \"\"\n";
+ output += "\n";
+}
+
+fs.writeFileSync(opt.options.output, output);
diff --git a/systemvm/agent/noVNC/po/zh_CN.po b/systemvm/agent/noVNC/po/zh_CN.po
new file mode 100644
index 0000000..78bfb95
--- /dev/null
+++ b/systemvm/agent/noVNC/po/zh_CN.po
@@ -0,0 +1,284 @@
+# Simplified Chinese translations for noVNC package.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Peter Dave Hello <hsu@peterdavehello.org>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.0.0-testing.2\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2018-01-10 00:53+0800\n"
+"PO-Revision-Date: 2018-04-06 21:33+0800\n"
+"Last-Translator: CUI Wei <ghostplant@qq.com>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../app/ui.js:395
+msgid "Connecting..."
+msgstr "链接中..."
+
+#: ../app/ui.js:402
+msgid "Disconnecting..."
+msgstr "正在中断连接..."
+
+#: ../app/ui.js:408
+msgid "Reconnecting..."
+msgstr "重新链接中..."
+
+#: ../app/ui.js:413
+msgid "Internal error"
+msgstr "内部错误"
+
+#: ../app/ui.js:1015
+msgid "Must set host"
+msgstr "请提供主机名"
+
+#: ../app/ui.js:1097
+msgid "Connected (encrypted) to "
+msgstr "已加密链接到"
+
+#: ../app/ui.js:1099
+msgid "Connected (unencrypted) to "
+msgstr "未加密链接到"
+
+#: ../app/ui.js:1120
+msgid "Something went wrong, connection is closed"
+msgstr "发生错误,链接已关闭"
+
+#: ../app/ui.js:1123
+msgid "Failed to connect to server"
+msgstr "无法链接到服务器"
+
+#: ../app/ui.js:1133
+msgid "Disconnected"
+msgstr "链接已中断"
+
+#: ../app/ui.js:1146
+msgid "New connection has been rejected with reason: "
+msgstr "链接被拒绝,原因:"
+
+#: ../app/ui.js:1149
+msgid "New connection has been rejected"
+msgstr "链接被拒绝"
+
+#: ../app/ui.js:1170
+msgid "Password is required"
+msgstr "请提供密码"
+
+#: ../vnc.html:89
+msgid "noVNC encountered an error:"
+msgstr "noVNC 遇到一个错误:"
+
+#: ../vnc.html:99
+msgid "Hide/Show the control bar"
+msgstr "显示/隐藏控制列"
+
+#: ../vnc.html:106
+msgid "Move/Drag Viewport"
+msgstr "拖放显示范围"
+
+#: ../vnc.html:106
+msgid "viewport drag"
+msgstr "显示范围拖放"
+
+#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
+msgid "Active Mouse Button"
+msgstr "启动鼠标按鍵"
+
+#: ../vnc.html:112
+msgid "No mousebutton"
+msgstr "禁用鼠标按鍵"
+
+#: ../vnc.html:115
+msgid "Left mousebutton"
+msgstr "鼠标左鍵"
+
+#: ../vnc.html:118
+msgid "Middle mousebutton"
+msgstr "鼠标中鍵"
+
+#: ../vnc.html:121
+msgid "Right mousebutton"
+msgstr "鼠标右鍵"
+
+#: ../vnc.html:124
+msgid "Keyboard"
+msgstr "键盘"
+
+#: ../vnc.html:124
+msgid "Show Keyboard"
+msgstr "显示键盘"
+
+#: ../vnc.html:131
+msgid "Extra keys"
+msgstr "额外按键"
+
+#: ../vnc.html:131
+msgid "Show Extra Keys"
+msgstr "显示额外按键"
+
+#: ../vnc.html:136
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:136
+msgid "Toggle Ctrl"
+msgstr "切换 Ctrl"
+
+#: ../vnc.html:139
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:139
+msgid "Toggle Alt"
+msgstr "切换 Alt"
+
+#: ../vnc.html:142
+msgid "Send Tab"
+msgstr "发送 Tab 键"
+
+#: ../vnc.html:142
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:145
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:145
+msgid "Send Escape"
+msgstr "发送 Escape 键"
+
+#: ../vnc.html:148
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl-Alt-Del"
+
+#: ../vnc.html:148
+msgid "Send Ctrl-Alt-Del"
+msgstr "发送 Ctrl-Alt-Del 键"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot"
+msgstr "关机/重新启动"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot..."
+msgstr "关机/重新启动..."
+
+#: ../vnc.html:162
+msgid "Power"
+msgstr "电源"
+
+#: ../vnc.html:164
+msgid "Shutdown"
+msgstr "关机"
+
+#: ../vnc.html:165
+msgid "Reboot"
+msgstr "重新启动"
+
+#: ../vnc.html:166
+msgid "Reset"
+msgstr "重置"
+
+#: ../vnc.html:171 ../vnc.html:177
+msgid "Clipboard"
+msgstr "剪贴板"
+
+#: ../vnc.html:181
+msgid "Clear"
+msgstr "清除"
+
+#: ../vnc.html:187
+msgid "Fullscreen"
+msgstr "全屏幕"
+
+#: ../vnc.html:192 ../vnc.html:199
+msgid "Settings"
+msgstr "设置"
+
+#: ../vnc.html:202
+msgid "Shared Mode"
+msgstr "分享模式"
+
+#: ../vnc.html:205
+msgid "View Only"
+msgstr "仅检视"
+
+#: ../vnc.html:209
+msgid "Clip to Window"
+msgstr "限制/裁切窗口大小"
+
+#: ../vnc.html:212
+msgid "Scaling Mode:"
+msgstr "缩放模式:"
+
+#: ../vnc.html:214
+msgid "None"
+msgstr "无"
+
+#: ../vnc.html:215
+msgid "Local Scaling"
+msgstr "本地缩放"
+
+#: ../vnc.html:216
+msgid "Remote Resizing"
+msgstr "远程调整大小"
+
+#: ../vnc.html:221
+msgid "Advanced"
+msgstr "高级"
+
+#: ../vnc.html:224
+msgid "Repeater ID:"
+msgstr "中继站 ID"
+
+#: ../vnc.html:228
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:231
+msgid "Encrypt"
+msgstr "加密"
+
+#: ../vnc.html:234
+msgid "Host:"
+msgstr "主机:"
+
+#: ../vnc.html:238
+msgid "Port:"
+msgstr "端口:"
+
+#: ../vnc.html:242
+msgid "Path:"
+msgstr "路径:"
+
+#: ../vnc.html:249
+msgid "Automatic Reconnect"
+msgstr "自动重新链接"
+
+#: ../vnc.html:252
+msgid "Reconnect Delay (ms):"
+msgstr "重新链接间隔 (ms):"
+
+#: ../vnc.html:258
+msgid "Logging:"
+msgstr "日志级别:"
+
+#: ../vnc.html:270
+msgid "Disconnect"
+msgstr "终端链接"
+
+#: ../vnc.html:289
+msgid "Connect"
+msgstr "链接"
+
+#: ../vnc.html:299
+msgid "Password:"
+msgstr "密码:"
+
+#: ../vnc.html:313
+msgid "Cancel"
+msgstr "取消"
diff --git a/systemvm/agent/noVNC/po/zh_TW.po b/systemvm/agent/noVNC/po/zh_TW.po
new file mode 100644
index 0000000..9ddf550
--- /dev/null
+++ b/systemvm/agent/noVNC/po/zh_TW.po
@@ -0,0 +1,285 @@
+# Traditional Chinese translations for noVNC package.
+# Copyright (C) 2018 The noVNC Authors
+# This file is distributed under the same license as the noVNC package.
+# Peter Dave Hello <hsu@peterdavehello.org>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 1.0.0-testing.2\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2018-01-10 00:53+0800\n"
+"PO-Revision-Date: 2018-01-10 01:33+0800\n"
+"Last-Translator: Peter Dave Hello <hsu@peterdavehello.org>\n"
+"Language-Team: Peter Dave Hello <hsu@peterdavehello.org>\n"
+"Language: zh\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../app/ui.js:395
+msgid "Connecting..."
+msgstr "連線中..."
+
+#: ../app/ui.js:402
+msgid "Disconnecting..."
+msgstr "正在中斷連線..."
+
+#: ../app/ui.js:408
+msgid "Reconnecting..."
+msgstr "重新連線中..."
+
+#: ../app/ui.js:413
+msgid "Internal error"
+msgstr "內部錯誤"
+
+#: ../app/ui.js:1015
+msgid "Must set host"
+msgstr "請提供主機資訊"
+
+#: ../app/ui.js:1097
+msgid "Connected (encrypted) to "
+msgstr "已加密連線到"
+
+#: ../app/ui.js:1099
+msgid "Connected (unencrypted) to "
+msgstr "未加密連線到"
+
+#: ../app/ui.js:1120
+msgid "Something went wrong, connection is closed"
+msgstr "發生錯誤,連線已關閉"
+
+#: ../app/ui.js:1123
+msgid "Failed to connect to server"
+msgstr "無法連線到伺服器"
+
+#: ../app/ui.js:1133
+msgid "Disconnected"
+msgstr "連線已中斷"
+
+#: ../app/ui.js:1146
+msgid "New connection has been rejected with reason: "
+msgstr "連線被拒絕,原因:"
+
+#: ../app/ui.js:1149
+msgid "New connection has been rejected"
+msgstr "連線被拒絕"
+
+#: ../app/ui.js:1170
+msgid "Password is required"
+msgstr "請提供密碼"
+
+#: ../vnc.html:89
+msgid "noVNC encountered an error:"
+msgstr "noVNC 遇到一個錯誤:"
+
+#: ../vnc.html:99
+msgid "Hide/Show the control bar"
+msgstr "顯示/隱藏控制列"
+
+#: ../vnc.html:106
+msgid "Move/Drag Viewport"
+msgstr "拖放顯示範圍"
+
+#: ../vnc.html:106
+msgid "viewport drag"
+msgstr "顯示範圍拖放"
+
+#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
+msgid "Active Mouse Button"
+msgstr "啟用滑鼠按鍵"
+
+#: ../vnc.html:112
+msgid "No mousebutton"
+msgstr "無滑鼠按鍵"
+
+#: ../vnc.html:115
+msgid "Left mousebutton"
+msgstr "滑鼠左鍵"
+
+#: ../vnc.html:118
+msgid "Middle mousebutton"
+msgstr "滑鼠中鍵"
+
+#: ../vnc.html:121
+msgid "Right mousebutton"
+msgstr "滑鼠右鍵"
+
+#: ../vnc.html:124
+msgid "Keyboard"
+msgstr "鍵盤"
+
+#: ../vnc.html:124
+msgid "Show Keyboard"
+msgstr "顯示鍵盤"
+
+#: ../vnc.html:131
+msgid "Extra keys"
+msgstr "額外按鍵"
+
+#: ../vnc.html:131
+msgid "Show Extra Keys"
+msgstr "顯示額外按鍵"
+
+#: ../vnc.html:136
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:136
+msgid "Toggle Ctrl"
+msgstr "切換 Ctrl"
+
+#: ../vnc.html:139
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:139
+msgid "Toggle Alt"
+msgstr "切換 Alt"
+
+#: ../vnc.html:142
+msgid "Send Tab"
+msgstr "送出 Tab 鍵"
+
+#: ../vnc.html:142
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:145
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:145
+msgid "Send Escape"
+msgstr "送出 Escape 鍵"
+
+#: ../vnc.html:148
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl-Alt-Del"
+
+#: ../vnc.html:148
+msgid "Send Ctrl-Alt-Del"
+msgstr "送出 Ctrl-Alt-Del 快捷鍵"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot"
+msgstr "關機/重新啟動"
+
+#: ../vnc.html:156
+msgid "Shutdown/Reboot..."
+msgstr "關機/重新啟動..."
+
+#: ../vnc.html:162
+msgid "Power"
+msgstr "電源"
+
+#: ../vnc.html:164
+msgid "Shutdown"
+msgstr "關機"
+
+#: ../vnc.html:165
+msgid "Reboot"
+msgstr "重新啟動"
+
+#: ../vnc.html:166
+msgid "Reset"
+msgstr "重設"
+
+#: ../vnc.html:171 ../vnc.html:177
+msgid "Clipboard"
+msgstr "剪貼簿"
+
+#: ../vnc.html:181
+msgid "Clear"
+msgstr "清除"
+
+#: ../vnc.html:187
+msgid "Fullscreen"
+msgstr "全螢幕"
+
+#: ../vnc.html:192 ../vnc.html:199
+msgid "Settings"
+msgstr "設定"
+
+#: ../vnc.html:202
+msgid "Shared Mode"
+msgstr "分享模式"
+
+#: ../vnc.html:205
+msgid "View Only"
+msgstr "僅檢視"
+
+#: ../vnc.html:209
+msgid "Clip to Window"
+msgstr "限制/裁切視窗大小"
+
+#: ../vnc.html:212
+msgid "Scaling Mode:"
+msgstr "縮放模式:"
+
+#: ../vnc.html:214
+msgid "None"
+msgstr "無"
+
+#: ../vnc.html:215
+msgid "Local Scaling"
+msgstr "本機縮放"
+
+#: ../vnc.html:216
+msgid "Remote Resizing"
+msgstr "遠端調整大小"
+
+#: ../vnc.html:221
+msgid "Advanced"
+msgstr "進階"
+
+#: ../vnc.html:224
+msgid "Repeater ID:"
+msgstr "中繼站 ID"
+
+#: ../vnc.html:228
+msgid "WebSocket"
+msgstr "WebSocket"
+
+#: ../vnc.html:231
+msgid "Encrypt"
+msgstr "加密"
+
+#: ../vnc.html:234
+msgid "Host:"
+msgstr "主機:"
+
+#: ../vnc.html:238
+msgid "Port:"
+msgstr "連接埠:"
+
+#: ../vnc.html:242
+msgid "Path:"
+msgstr "路徑:"
+
+#: ../vnc.html:249
+msgid "Automatic Reconnect"
+msgstr "自動重新連線"
+
+#: ../vnc.html:252
+msgid "Reconnect Delay (ms):"
+msgstr "重新連線間隔 (ms):"
+
+#: ../vnc.html:258
+msgid "Logging:"
+msgstr "日誌級別:"
+
+#: ../vnc.html:270
+msgid "Disconnect"
+msgstr "中斷連線"
+
+#: ../vnc.html:289
+msgid "Connect"
+msgstr "連線"
+
+#: ../vnc.html:299
+msgid "Password:"
+msgstr "密碼:"
+
+#: ../vnc.html:313
+msgid "Cancel"
+msgstr "取消"
diff --git a/systemvm/agent/noVNC/tests/.eslintrc b/systemvm/agent/noVNC/tests/.eslintrc
new file mode 100644
index 0000000..545fa2e
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/.eslintrc
@@ -0,0 +1,15 @@
+{
+ "env": {
+ "node": true,
+ "mocha": true
+ },
+ "globals": {
+ "chai": false,
+ "sinon": false
+ },
+ "rules": {
+ "prefer-arrow-callback": 0,
+ // Too many anonymous callbacks
+ "func-names": "off",
+ }
+}
diff --git a/systemvm/agent/noVNC/tests/assertions.js b/systemvm/agent/noVNC/tests/assertions.js
new file mode 100644
index 0000000..07a5c29
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/assertions.js
@@ -0,0 +1,101 @@
+// noVNC specific assertions
+chai.use(function (_chai, utils) {
+ _chai.Assertion.addMethod('displayed', function (target_data) {
+ const obj = this._obj;
+ const ctx = obj._target.getContext('2d');
+ const data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
+ // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
+ const data = new Uint8Array(data_cl);
+ const len = data_cl.length;
+ new chai.Assertion(len).to.be.equal(target_data.length, "unexpected display size");
+ let same = true;
+ for (let i = 0; i < len; i++) {
+ if (data[i] != target_data[i]) {
+ same = false;
+ break;
+ }
+ }
+ if (!same) {
+ // eslint-disable-next-line no-console
+ console.log("expected data: %o, actual data: %o", target_data, data);
+ }
+ this.assert(same,
+ "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
+ "expected #{this} not to have displayed the image #{act}",
+ target_data,
+ data);
+ });
+
+ _chai.Assertion.addMethod('sent', function (target_data) {
+ const obj = this._obj;
+ obj.inspect = () => {
+ const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
+ _sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
+ res.prototype = obj;
+ return res;
+ };
+ const data = obj._websocket._get_sent_data();
+ let same = true;
+ if (data.length != target_data.length) {
+ same = false;
+ } else {
+ for (let i = 0; i < data.length; i++) {
+ if (data[i] != target_data[i]) {
+ same = false;
+ break;
+ }
+ }
+ }
+ if (!same) {
+ // eslint-disable-next-line no-console
+ console.log("expected data: %o, actual data: %o", target_data, data);
+ }
+ this.assert(same,
+ "expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
+ "expected #{this} not to have sent the data #{act}",
+ Array.prototype.slice.call(target_data),
+ Array.prototype.slice.call(data));
+ });
+
+ _chai.Assertion.addProperty('array', function () {
+ utils.flag(this, 'array', true);
+ });
+
+ _chai.Assertion.overwriteMethod('equal', function (_super) {
+ return function assertArrayEqual(target) {
+ if (utils.flag(this, 'array')) {
+ const obj = this._obj;
+
+ let same = true;
+
+ if (utils.flag(this, 'deep')) {
+ for (let i = 0; i < obj.length; i++) {
+ if (!utils.eql(obj[i], target[i])) {
+ same = false;
+ break;
+ }
+ }
+
+ this.assert(same,
+ "expected #{this} to have elements deeply equal to #{exp}",
+ "expected #{this} not to have elements deeply equal to #{exp}",
+ Array.prototype.slice.call(target));
+ } else {
+ for (let i = 0; i < obj.length; i++) {
+ if (obj[i] != target[i]) {
+ same = false;
+ break;
+ }
+ }
+
+ this.assert(same,
+ "expected #{this} to have elements equal to #{exp}",
+ "expected #{this} not to have elements equal to #{exp}",
+ Array.prototype.slice.call(target));
+ }
+ } else {
+ _super.apply(this, arguments);
+ }
+ };
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/fake.websocket.js b/systemvm/agent/noVNC/tests/fake.websocket.js
new file mode 100644
index 0000000..68ab3f8
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/fake.websocket.js
@@ -0,0 +1,96 @@
+import Base64 from '../core/base64.js';
+
+// PhantomJS can't create Event objects directly, so we need to use this
+function make_event(name, props) {
+ const evt = document.createEvent('Event');
+ evt.initEvent(name, true, true);
+ if (props) {
+ for (let prop in props) {
+ evt[prop] = props[prop];
+ }
+ }
+ return evt;
+}
+
+export default class FakeWebSocket {
+ constructor(uri, protocols) {
+ this.url = uri;
+ this.binaryType = "arraybuffer";
+ this.extensions = "";
+
+ if (!protocols || typeof protocols === 'string') {
+ this.protocol = protocols;
+ } else {
+ this.protocol = protocols[0];
+ }
+
+ this._send_queue = new Uint8Array(20000);
+
+ this.readyState = FakeWebSocket.CONNECTING;
+ this.bufferedAmount = 0;
+
+ this.__is_fake = true;
+ }
+
+ close(code, reason) {
+ this.readyState = FakeWebSocket.CLOSED;
+ if (this.onclose) {
+ this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true }));
+ }
+ }
+
+ send(data) {
+ if (this.protocol == 'base64') {
+ data = Base64.decode(data);
+ } else {
+ data = new Uint8Array(data);
+ }
+ this._send_queue.set(data, this.bufferedAmount);
+ this.bufferedAmount += data.length;
+ }
+
+ _get_sent_data() {
+ const res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount);
+ this.bufferedAmount = 0;
+ return res;
+ }
+
+ _open() {
+ this.readyState = FakeWebSocket.OPEN;
+ if (this.onopen) {
+ this.onopen(make_event('open'));
+ }
+ }
+
+ _receive_data(data) {
+ // Break apart the data to expose bugs where we assume data is
+ // neatly packaged
+ for (let i = 0;i < data.length;i++) {
+ let buf = data.subarray(i, i+1);
+ this.onmessage(make_event("message", { 'data': buf }));
+ }
+ }
+}
+
+FakeWebSocket.OPEN = WebSocket.OPEN;
+FakeWebSocket.CONNECTING = WebSocket.CONNECTING;
+FakeWebSocket.CLOSING = WebSocket.CLOSING;
+FakeWebSocket.CLOSED = WebSocket.CLOSED;
+
+FakeWebSocket.__is_fake = true;
+
+FakeWebSocket.replace = () => {
+ if (!WebSocket.__is_fake) {
+ const real_version = WebSocket;
+ // eslint-disable-next-line no-global-assign
+ WebSocket = FakeWebSocket;
+ FakeWebSocket.__real_version = real_version;
+ }
+};
+
+FakeWebSocket.restore = () => {
+ if (WebSocket.__is_fake) {
+ // eslint-disable-next-line no-global-assign
+ WebSocket = WebSocket.__real_version;
+ }
+};
diff --git a/systemvm/agent/noVNC/tests/karma-test-main.js b/systemvm/agent/noVNC/tests/karma-test-main.js
new file mode 100644
index 0000000..2843666
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/karma-test-main.js
@@ -0,0 +1,48 @@
+const TEST_REGEXP = /test\..*\.js/;
+const allTestFiles = [];
+const extraFiles = ['/base/tests/assertions.js'];
+
+Object.keys(window.__karma__.files).forEach(function (file) {
+ if (TEST_REGEXP.test(file)) {
+ // TODO: normalize?
+ allTestFiles.push(file);
+ }
+});
+
+// Stub out mocha's start function so we can run it once we're done loading
+mocha.origRun = mocha.run;
+mocha.run = function () {};
+
+let script;
+
+// Script to import all our tests
+script = document.createElement("script");
+script.type = "module";
+script.text = "";
+let allModules = allTestFiles.concat(extraFiles);
+allModules.forEach(function (file) {
+ script.text += "import \"" + file + "\";\n";
+});
+script.text += "\nmocha.origRun();\n";
+document.body.appendChild(script);
+
+// Fallback code for browsers that don't support modules (IE)
+script = document.createElement("script");
+script.type = "module";
+script.text = "window._noVNC_has_module_support = true;\n";
+document.body.appendChild(script);
+
+function fallback() {
+ if (!window._noVNC_has_module_support) {
+ /* eslint-disable no-console */
+ if (console) {
+ console.log("No module support detected. Loading fallback...");
+ }
+ /* eslint-enable no-console */
+ let loader = document.createElement("script");
+ loader.src = "base/vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
+ document.body.appendChild(loader);
+ }
+}
+
+setTimeout(fallback, 500);
diff --git a/systemvm/agent/noVNC/tests/playback-ui.js b/systemvm/agent/noVNC/tests/playback-ui.js
new file mode 100644
index 0000000..65c715a
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/playback-ui.js
@@ -0,0 +1,210 @@
+/* global VNC_frame_data, VNC_frame_encoding */
+
+import * as WebUtil from '../app/webutil.js';
+import RecordingPlayer from './playback.js';
+import Base64 from '../core/base64.js';
+
+let frames = null;
+
+function message(str) {
+ const cell = document.getElementById('messages');
+ cell.textContent += str + "\n";
+ cell.scrollTop = cell.scrollHeight;
+}
+
+function loadFile() {
+ const fname = WebUtil.getQueryVar('data', null);
+
+ if (!fname) {
+ return Promise.reject("Must specify data=FOO in query string.");
+ }
+
+ message("Loading " + fname + "...");
+
+ return new Promise((resolve, reject) => {
+ const script = document.createElement("script");
+ script.onload = resolve;
+ script.onerror = reject;
+ document.body.appendChild(script);
+ script.src = "../recordings/" + fname;
+ });
+}
+
+function enableUI() {
+ const iterations = WebUtil.getQueryVar('iterations', 3);
+ document.getElementById('iterations').value = iterations;
+
+ const mode = WebUtil.getQueryVar('mode', 3);
+ if (mode === 'realtime') {
+ document.getElementById('mode2').checked = true;
+ } else {
+ document.getElementById('mode1').checked = true;
+ }
+
+ message("Loaded " + VNC_frame_data.length + " frames");
+
+ const startButton = document.getElementById('startButton');
+ startButton.disabled = false;
+ startButton.addEventListener('click', start);
+
+ message("Converting...");
+
+ frames = VNC_frame_data;
+
+ let encoding;
+ // Only present in older recordings
+ if (window.VNC_frame_encoding) {
+ encoding = VNC_frame_encoding;
+ } else {
+ let frame = frames[0];
+ let start = frame.indexOf('{', 1) + 1;
+ if (frame.slice(start, start+4) === 'UkZC') {
+ encoding = 'base64';
+ } else {
+ encoding = 'binary';
+ }
+ }
+
+ for (let i = 0;i < frames.length;i++) {
+ let frame = frames[i];
+
+ if (frame === "EOF") {
+ frames.splice(i);
+ break;
+ }
+
+ let dataIdx = frame.indexOf('{', 1) + 1;
+
+ let time = parseInt(frame.slice(1, dataIdx - 1));
+
+ let u8;
+ if (encoding === 'base64') {
+ u8 = Base64.decode(frame.slice(dataIdx));
+ } else {
+ u8 = new Uint8Array(frame.length - dataIdx);
+ for (let j = 0; j < frame.length - dataIdx; j++) {
+ u8[j] = frame.charCodeAt(dataIdx + j);
+ }
+ }
+
+ frames[i] = { fromClient: frame[0] === '}',
+ timestamp: time,
+ data: u8 };
+ }
+
+ message("Ready");
+}
+
+class IterationPlayer {
+ constructor(iterations, frames) {
+ this._iterations = iterations;
+
+ this._iteration = undefined;
+ this._player = undefined;
+
+ this._start_time = undefined;
+
+ this._frames = frames;
+
+ this._state = 'running';
+
+ this.onfinish = () => {};
+ this.oniterationfinish = () => {};
+ this.rfbdisconnected = () => {};
+ }
+
+ start(realtime) {
+ this._iteration = 0;
+ this._start_time = (new Date()).getTime();
+
+ this._realtime = realtime;
+
+ this._nextIteration();
+ }
+
+ _nextIteration() {
+ const player = new RecordingPlayer(this._frames, this._disconnected.bind(this));
+ player.onfinish = this._iterationFinish.bind(this);
+
+ if (this._state !== 'running') { return; }
+
+ this._iteration++;
+ if (this._iteration > this._iterations) {
+ this._finish();
+ return;
+ }
+
+ player.run(this._realtime, false);
+ }
+
+ _finish() {
+ const endTime = (new Date()).getTime();
+ const totalDuration = endTime - this._start_time;
+
+ const evt = new CustomEvent('finish',
+ { detail:
+ { duration: totalDuration,
+ iterations: this._iterations } } );
+ this.onfinish(evt);
+ }
+
+ _iterationFinish(duration) {
+ const evt = new CustomEvent('iterationfinish',
+ { detail:
+ { duration: duration,
+ number: this._iteration } } );
+ this.oniterationfinish(evt);
+
+ this._nextIteration();
+ }
+
+ _disconnected(clean, frame) {
+ if (!clean) {
+ this._state = 'failed';
+ }
+
+ const evt = new CustomEvent('rfbdisconnected',
+ { detail:
+ { clean: clean,
+ frame: frame,
+ iteration: this._iteration } } );
+ this.onrfbdisconnected(evt);
+ }
+}
+
+function start() {
+ document.getElementById('startButton').value = "Running";
+ document.getElementById('startButton').disabled = true;
+
+ const iterations = document.getElementById('iterations').value;
+
+ let realtime;
+
+ if (document.getElementById('mode1').checked) {
+ message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`);
+ realtime = false;
+ } else {
+ message(`Starting realtime playback [${iterations} iteration(s)]`);
+ realtime = true;
+ }
+
+ const player = new IterationPlayer(iterations, frames);
+ player.oniterationfinish = (evt) => {
+ message(`Iteration ${evt.detail.number} took ${evt.detail.duration}ms`);
+ };
+ player.onrfbdisconnected = (evt) => {
+ if (!evt.detail.clean) {
+ message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
+ }
+ };
+ player.onfinish = (evt) => {
+ const iterTime = parseInt(evt.detail.duration / evt.detail.iterations, 10);
+ message(`${evt.detail.iterations} iterations took ${evt.detail.duration}ms (average ${iterTime}ms / iteration)`);
+
+ document.getElementById('startButton').disabled = false;
+ document.getElementById('startButton').value = "Start";
+ };
+ player.start(realtime);
+}
+
+loadFile().then(enableUI).catch(e => message("Error loading recording: " + e));
diff --git a/systemvm/agent/noVNC/tests/playback.js b/systemvm/agent/noVNC/tests/playback.js
new file mode 100644
index 0000000..5bd8103
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/playback.js
@@ -0,0 +1,172 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ */
+
+import RFB from '../core/rfb.js';
+import * as Log from '../core/util/logging.js';
+
+// Immediate polyfill
+if (window.setImmediate === undefined) {
+ let _immediateIdCounter = 1;
+ const _immediateFuncs = {};
+
+ window.setImmediate = (func) => {
+ const index = _immediateIdCounter++;
+ _immediateFuncs[index] = func;
+ window.postMessage("noVNC immediate trigger:" + index, "*");
+ return index;
+ };
+
+ window.clearImmediate = (id) => {
+ _immediateFuncs[id];
+ };
+
+ window.addEventListener("message", (event) => {
+ if ((typeof event.data !== "string") ||
+ (event.data.indexOf("noVNC immediate trigger:") !== 0)) {
+ return;
+ }
+
+ const index = event.data.slice("noVNC immediate trigger:".length);
+
+ const callback = _immediateFuncs[index];
+ if (callback === undefined) {
+ return;
+ }
+
+ delete _immediateFuncs[index];
+
+ callback();
+ });
+}
+
+export default class RecordingPlayer {
+ constructor(frames, disconnected) {
+ this._frames = frames;
+
+ this._disconnected = disconnected;
+
+ this._rfb = undefined;
+ this._frame_length = this._frames.length;
+
+ this._frame_index = 0;
+ this._start_time = undefined;
+ this._realtime = true;
+ this._trafficManagement = true;
+
+ this._running = false;
+
+ this.onfinish = () => {};
+ }
+
+ run(realtime, trafficManagement) {
+ // initialize a new RFB
+ this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
+ this._rfb.viewOnly = true;
+ this._rfb.addEventListener("disconnect",
+ this._handleDisconnect.bind(this));
+ this._rfb.addEventListener("credentialsrequired",
+ this._handleCredentials.bind(this));
+ this._enablePlaybackMode();
+
+ // reset the frame index and timer
+ this._frame_index = 0;
+ this._start_time = (new Date()).getTime();
+
+ this._realtime = realtime;
+ this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
+
+ this._running = true;
+ }
+
+ // _enablePlaybackMode mocks out things not required for running playback
+ _enablePlaybackMode() {
+ const self = this;
+ this._rfb._sock.send = () => {};
+ this._rfb._sock.close = () => {};
+ this._rfb._sock.flush = () => {};
+ this._rfb._sock.open = function () {
+ this.init();
+ this._eventHandlers.open();
+ self._queueNextPacket();
+ };
+ }
+
+ _queueNextPacket() {
+ if (!this._running) { return; }
+
+ let frame = this._frames[this._frame_index];
+
+ // skip send frames
+ while (this._frame_index < this._frame_length && frame.fromClient) {
+ this._frame_index++;
+ frame = this._frames[this._frame_index];
+ }
+
+ if (this._frame_index >= this._frame_length) {
+ Log.Debug('Finished, no more frames');
+ this._finish();
+ return;
+ }
+
+ if (this._realtime) {
+ const toffset = (new Date()).getTime() - this._start_time;
+ let delay = frame.timestamp - toffset;
+ if (delay < 1) delay = 1;
+
+ setTimeout(this._doPacket.bind(this), delay);
+ } else {
+ setImmediate(this._doPacket.bind(this));
+ }
+ }
+
+ _doPacket() {
+ // Avoid having excessive queue buildup in non-realtime mode
+ if (this._trafficManagement && this._rfb._flushing) {
+ const orig = this._rfb._display.onflush;
+ this._rfb._display.onflush = () => {
+ this._rfb._display.onflush = orig;
+ this._rfb._onFlush();
+ this._doPacket();
+ };
+ return;
+ }
+
+ const frame = this._frames[this._frame_index];
+
+ this._rfb._sock._recv_message({'data': frame.data});
+ this._frame_index++;
+
+ this._queueNextPacket();
+ }
+
+ _finish() {
+ if (this._rfb._display.pending()) {
+ this._rfb._display.onflush = () => {
+ if (this._rfb._flushing) {
+ this._rfb._onFlush();
+ }
+ this._finish();
+ };
+ this._rfb._display.flush();
+ } else {
+ this._running = false;
+ this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
+ delete this._rfb;
+ this.onfinish((new Date()).getTime() - this._start_time);
+ }
+ }
+
+ _handleDisconnect(evt) {
+ this._running = false;
+ this._disconnected(evt.detail.clean, this._frame_index);
+ }
+
+ _handleCredentials(evt) {
+ this._rfb.sendCredentials({"username": "Foo",
+ "password": "Bar",
+ "target": "Baz"});
+ }
+}
diff --git a/systemvm/agent/noVNC/tests/test.base64.js b/systemvm/agent/noVNC/tests/test.base64.js
new file mode 100644
index 0000000..04bd207
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.base64.js
@@ -0,0 +1,33 @@
+const expect = chai.expect;
+
+import Base64 from '../core/base64.js';
+
+describe('Base64 Tools', function () {
+ "use strict";
+
+ const BIN_ARR = new Array(256);
+ for (let i = 0; i < 256; i++) {
+ BIN_ARR[i] = i;
+ }
+
+ const B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
+
+
+ describe('encode', function () {
+ it('should encode a binary string into Base64', function () {
+ const encoded = Base64.encode(BIN_ARR);
+ expect(encoded).to.equal(B64_STR);
+ });
+ });
+
+ describe('decode', function () {
+ it('should decode a Base64 string into a normal string', function () {
+ const decoded = Base64.decode(B64_STR);
+ expect(decoded).to.deep.equal(BIN_ARR);
+ });
+
+ it('should throw an error if we have extra characters at the end of the string', function () {
+ expect(() => Base64.decode(B64_STR+'abcdef')).to.throw(Error);
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/test.display.js b/systemvm/agent/noVNC/tests/test.display.js
new file mode 100644
index 0000000..b359550
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.display.js
@@ -0,0 +1,486 @@
+const expect = chai.expect;
+
+import Base64 from '../core/base64.js';
+import Display from '../core/display.js';
+
+describe('Display/Canvas Helper', function () {
+ const checked_data = new Uint8Array([
+ 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
+ 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
+ 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
+ 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
+ ]);
+
+ const basic_data = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
+
+ function make_image_canvas(input_data) {
+ const canvas = document.createElement('canvas');
+ canvas.width = 4;
+ canvas.height = 4;
+ const ctx = canvas.getContext('2d');
+ const data = ctx.createImageData(4, 4);
+ for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
+ ctx.putImageData(data, 0, 0);
+ return canvas;
+ }
+
+ function make_image_png(input_data) {
+ const canvas = make_image_canvas(input_data);
+ const url = canvas.toDataURL();
+ const data = url.split(",")[1];
+ return Base64.decode(data);
+ }
+
+ describe('viewport handling', function () {
+ let display;
+ beforeEach(function () {
+ display = new Display(document.createElement('canvas'));
+ display.clipViewport = true;
+ display.resize(5, 5);
+ display.viewportChangeSize(3, 3);
+ display.viewportChangePos(1, 1);
+ });
+
+ it('should take viewport location into consideration when drawing images', function () {
+ display.resize(4, 4);
+ display.viewportChangeSize(2, 2);
+ display.drawImage(make_image_canvas(basic_data), 1, 1);
+ display.flip();
+
+ const expected = new Uint8Array(16);
+ for (let i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
+ for (let i = 8; i < 16; i++) { expected[i] = 0; }
+ expect(display).to.have.displayed(expected);
+ });
+
+ it('should resize the target canvas when resizing the viewport', function () {
+ display.viewportChangeSize(2, 2);
+ expect(display._target.width).to.equal(2);
+ expect(display._target.height).to.equal(2);
+ });
+
+ it('should move the viewport if necessary', function () {
+ display.viewportChangeSize(5, 5);
+ expect(display.absX(0)).to.equal(0);
+ expect(display.absY(0)).to.equal(0);
+ expect(display._target.width).to.equal(5);
+ expect(display._target.height).to.equal(5);
+ });
+
+ it('should limit the viewport to the framebuffer size', function () {
+ display.viewportChangeSize(6, 6);
+ expect(display._target.width).to.equal(5);
+ expect(display._target.height).to.equal(5);
+ });
+
+ it('should redraw when moving the viewport', function () {
+ display.flip = sinon.spy();
+ display.viewportChangePos(-1, 1);
+ expect(display.flip).to.have.been.calledOnce;
+ });
+
+ it('should redraw when resizing the viewport', function () {
+ display.flip = sinon.spy();
+ display.viewportChangeSize(2, 2);
+ expect(display.flip).to.have.been.calledOnce;
+ });
+
+ it('should show the entire framebuffer when disabling the viewport', function () {
+ display.clipViewport = false;
+ expect(display.absX(0)).to.equal(0);
+ expect(display.absY(0)).to.equal(0);
+ expect(display._target.width).to.equal(5);
+ expect(display._target.height).to.equal(5);
+ });
+
+ it('should ignore viewport changes when the viewport is disabled', function () {
+ display.clipViewport = false;
+ display.viewportChangeSize(2, 2);
+ display.viewportChangePos(1, 1);
+ expect(display.absX(0)).to.equal(0);
+ expect(display.absY(0)).to.equal(0);
+ expect(display._target.width).to.equal(5);
+ expect(display._target.height).to.equal(5);
+ });
+
+ it('should show the entire framebuffer just after enabling the viewport', function () {
+ display.clipViewport = false;
+ display.clipViewport = true;
+ expect(display.absX(0)).to.equal(0);
+ expect(display.absY(0)).to.equal(0);
+ expect(display._target.width).to.equal(5);
+ expect(display._target.height).to.equal(5);
+ });
+ });
+
+ describe('resizing', function () {
+ let display;
+ beforeEach(function () {
+ display = new Display(document.createElement('canvas'));
+ display.clipViewport = false;
+ display.resize(4, 4);
+ });
+
+ it('should change the size of the logical canvas', function () {
+ display.resize(5, 7);
+ expect(display._fb_width).to.equal(5);
+ expect(display._fb_height).to.equal(7);
+ });
+
+ it('should keep the framebuffer data', function () {
+ display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
+ display.resize(2, 2);
+ display.flip();
+ const expected = [];
+ for (let i = 0; i < 4 * 2*2; i += 4) {
+ expected[i] = 0xff;
+ expected[i+1] = expected[i+2] = 0;
+ expected[i+3] = 0xff;
+ }
+ expect(display).to.have.displayed(new Uint8Array(expected));
+ });
+
+ describe('viewport', function () {
+ beforeEach(function () {
+ display.clipViewport = true;
+ display.viewportChangeSize(3, 3);
+ display.viewportChangePos(1, 1);
+ });
+
+ it('should keep the viewport position and size if possible', function () {
+ display.resize(6, 6);
+ expect(display.absX(0)).to.equal(1);
+ expect(display.absY(0)).to.equal(1);
+ expect(display._target.width).to.equal(3);
+ expect(display._target.height).to.equal(3);
+ });
+
+ it('should move the viewport if necessary', function () {
+ display.resize(3, 3);
+ expect(display.absX(0)).to.equal(0);
+ expect(display.absY(0)).to.equal(0);
+ expect(display._target.width).to.equal(3);
+ expect(display._target.height).to.equal(3);
+ });
+
+ it('should shrink the viewport if necessary', function () {
+ display.resize(2, 2);
+ expect(display.absX(0)).to.equal(0);
+ expect(display.absY(0)).to.equal(0);
+ expect(display._target.width).to.equal(2);
+ expect(display._target.height).to.equal(2);
+ });
+ });
+ });
+
+ describe('rescaling', function () {
+ let display;
+ let canvas;
+
+ beforeEach(function () {
+ canvas = document.createElement('canvas');
+ display = new Display(canvas);
+ display.clipViewport = true;
+ display.resize(4, 4);
+ display.viewportChangeSize(3, 3);
+ display.viewportChangePos(1, 1);
+ document.body.appendChild(canvas);
+ });
+
+ afterEach(function () {
+ document.body.removeChild(canvas);
+ });
+
+ it('should not change the bitmap size of the canvas', function () {
+ display.scale = 2.0;
+ expect(canvas.width).to.equal(3);
+ expect(canvas.height).to.equal(3);
+ });
+
+ it('should change the effective rendered size of the canvas', function () {
+ display.scale = 2.0;
+ expect(canvas.clientWidth).to.equal(6);
+ expect(canvas.clientHeight).to.equal(6);
+ });
+
+ it('should not change when resizing', function () {
+ display.scale = 2.0;
+ display.resize(5, 5);
+ expect(display.scale).to.equal(2.0);
+ expect(canvas.width).to.equal(3);
+ expect(canvas.height).to.equal(3);
+ expect(canvas.clientWidth).to.equal(6);
+ expect(canvas.clientHeight).to.equal(6);
+ });
+ });
+
+ describe('autoscaling', function () {
+ let display;
+ let canvas;
+
+ beforeEach(function () {
+ canvas = document.createElement('canvas');
+ display = new Display(canvas);
+ display.clipViewport = true;
+ display.resize(4, 3);
+ document.body.appendChild(canvas);
+ });
+
+ afterEach(function () {
+ document.body.removeChild(canvas);
+ });
+
+ it('should preserve aspect ratio while autoscaling', function () {
+ display.autoscale(16, 9);
+ expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
+ });
+
+ it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
+ display.autoscale(9, 16);
+ expect(display.absX(9)).to.equal(4);
+ expect(display.absY(18)).to.equal(8);
+ expect(canvas.clientWidth).to.equal(9);
+ expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
+ });
+
+ it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
+ display.autoscale(16, 9);
+ expect(display.absX(9)).to.equal(3);
+ expect(display.absY(18)).to.equal(6);
+ expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
+ expect(canvas.clientHeight).to.equal(9);
+
+ });
+
+ it('should not change the bitmap size of the canvas', function () {
+ display.autoscale(16, 9);
+ expect(canvas.width).to.equal(4);
+ expect(canvas.height).to.equal(3);
+ });
+ });
+
+ describe('drawing', function () {
+
+ // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
+ // basic cases
+ let display;
+ beforeEach(function () {
+ display = new Display(document.createElement('canvas'));
+ display.resize(4, 4);
+ });
+
+ it('should clear the screen on #clear without a logo set', function () {
+ display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
+ display._logo = null;
+ display.clear();
+ display.resize(4, 4);
+ const empty = [];
+ for (let i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
+ expect(display).to.have.displayed(new Uint8Array(empty));
+ });
+
+ it('should draw the logo on #clear with a logo set', function (done) {
+ display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) };
+ display.clear();
+ display.onflush = () => {
+ expect(display).to.have.displayed(checked_data);
+ expect(display._fb_width).to.equal(4);
+ expect(display._fb_height).to.equal(4);
+ done();
+ };
+ display.flush();
+ });
+
+ it('should not draw directly on the target canvas', function () {
+ display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
+ display.flip();
+ display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
+ const expected = [];
+ for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) {
+ expected[i] = 0xff;
+ expected[i+1] = expected[i+2] = 0;
+ expected[i+3] = 0xff;
+ }
+ expect(display).to.have.displayed(new Uint8Array(expected));
+ });
+
+ it('should support filling a rectangle with particular color via #fillRect', function () {
+ display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
+ display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
+ display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
+ display.flip();
+ expect(display).to.have.displayed(checked_data);
+ });
+
+ it('should support copying an portion of the canvas via #copyImage', function () {
+ display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
+ display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
+ display.copyImage(0, 0, 2, 2, 2, 2);
+ display.flip();
+ expect(display).to.have.displayed(checked_data);
+ });
+
+ it('should support drawing images via #imageRect', function (done) {
+ display.imageRect(0, 0, "image/png", make_image_png(checked_data));
+ display.flip();
+ display.onflush = () => {
+ expect(display).to.have.displayed(checked_data);
+ done();
+ };
+ display.flush();
+ });
+
+ it('should support drawing tile data with a background color and sub tiles', function () {
+ display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
+ display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
+ display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
+ display.finishTile();
+ display.flip();
+ expect(display).to.have.displayed(checked_data);
+ });
+
+ // We have a special cache for 16x16 tiles that we need to test
+ it('should support drawing a 16x16 tile', function () {
+ const large_checked_data = new Uint8Array(16*16*4);
+ display.resize(16, 16);
+
+ for (let y = 0;y < 16;y++) {
+ for (let x = 0;x < 16;x++) {
+ let pixel;
+ if ((x < 4) && (y < 4)) {
+ // NB: of course IE11 doesn't support #slice on ArrayBufferViews...
+ pixel = Array.prototype.slice.call(checked_data, (y*4+x)*4, (y*4+x+1)*4);
+ } else {
+ pixel = [0, 0xff, 0, 255];
+ }
+ large_checked_data.set(pixel, (y*16+x)*4);
+ }
+ }
+
+ display.startTile(0, 0, 16, 16, [0, 0xff, 0]);
+ display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
+ display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
+ display.finishTile();
+ display.flip();
+ expect(display).to.have.displayed(large_checked_data);
+ });
+
+ it('should support drawing BGRX blit images with true color via #blitImage', function () {
+ const data = [];
+ for (let i = 0; i < 16; i++) {
+ data[i * 4] = checked_data[i * 4 + 2];
+ data[i * 4 + 1] = checked_data[i * 4 + 1];
+ data[i * 4 + 2] = checked_data[i * 4];
+ data[i * 4 + 3] = checked_data[i * 4 + 3];
+ }
+ display.blitImage(0, 0, 4, 4, data, 0);
+ display.flip();
+ expect(display).to.have.displayed(checked_data);
+ });
+
+ it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
+ const data = [];
+ for (let i = 0; i < 16; i++) {
+ data[i * 3] = checked_data[i * 4];
+ data[i * 3 + 1] = checked_data[i * 4 + 1];
+ data[i * 3 + 2] = checked_data[i * 4 + 2];
+ }
+ display.blitRgbImage(0, 0, 4, 4, data, 0);
+ display.flip();
+ expect(display).to.have.displayed(checked_data);
+ });
+
+ it('should support drawing an image object via #drawImage', function () {
+ const img = make_image_canvas(checked_data);
+ display.drawImage(img, 0, 0);
+ display.flip();
+ expect(display).to.have.displayed(checked_data);
+ });
+ });
+
+ describe('the render queue processor', function () {
+ let display;
+ beforeEach(function () {
+ display = new Display(document.createElement('canvas'));
+ display.resize(4, 4);
+ sinon.spy(display, '_scan_renderQ');
+ });
+
+ afterEach(function () {
+ window.requestAnimationFrame = this.old_requestAnimationFrame;
+ });
+
+ it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
+ display._renderQ_push({ type: 'noop' }); // does nothing
+ expect(display._scan_renderQ).to.have.been.calledOnce;
+ });
+
+ it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
+ display._renderQ.length = 2;
+ display._renderQ_push({ type: 'noop' });
+ expect(display._scan_renderQ).to.not.have.been.called;
+ });
+
+ it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
+ const img = { complete: false, addEventListener: sinon.spy() };
+ display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
+ { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
+ display.drawImage = sinon.spy();
+ display.fillRect = sinon.spy();
+
+ display._scan_renderQ();
+ expect(display.drawImage).to.not.have.been.called;
+ expect(display.fillRect).to.not.have.been.called;
+ expect(img.addEventListener).to.have.been.calledOnce;
+
+ display._renderQ[0].img.complete = true;
+ display._scan_renderQ();
+ expect(display.drawImage).to.have.been.calledOnce;
+ expect(display.fillRect).to.have.been.calledOnce;
+ expect(img.addEventListener).to.have.been.calledOnce;
+ });
+
+ it('should call callback when queue is flushed', function () {
+ display.onflush = sinon.spy();
+ display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
+ expect(display.onflush).to.not.have.been.called;
+ display.flush();
+ expect(display.onflush).to.have.been.calledOnce;
+ });
+
+ it('should draw a blit image on type "blit"', function () {
+ display.blitImage = sinon.spy();
+ display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
+ expect(display.blitImage).to.have.been.calledOnce;
+ expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
+ });
+
+ it('should draw a blit RGB image on type "blitRgb"', function () {
+ display.blitRgbImage = sinon.spy();
+ display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
+ expect(display.blitRgbImage).to.have.been.calledOnce;
+ expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
+ });
+
+ it('should copy a region on type "copy"', function () {
+ display.copyImage = sinon.spy();
+ display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
+ expect(display.copyImage).to.have.been.calledOnce;
+ expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
+ });
+
+ it('should fill a rect with a given color on type "fill"', function () {
+ display.fillRect = sinon.spy();
+ display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
+ expect(display.fillRect).to.have.been.calledOnce;
+ expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
+ });
+
+ it('should draw an image from an image object on type "img" (if complete)', function () {
+ display.drawImage = sinon.spy();
+ display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
+ expect(display.drawImage).to.have.been.calledOnce;
+ expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/test.helper.js b/systemvm/agent/noVNC/tests/test.helper.js
new file mode 100644
index 0000000..d44bab0
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.helper.js
@@ -0,0 +1,223 @@
+const expect = chai.expect;
+
+import keysyms from '../core/input/keysymdef.js';
+import * as KeyboardUtil from "../core/input/util.js";
+import * as browser from '../core/util/browser.js';
+
+describe('Helpers', function () {
+ "use strict";
+
+ describe('keysyms.lookup', function () {
+ it('should map ASCII characters to keysyms', function () {
+ expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61);
+ expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41);
+ });
+ it('should map Latin-1 characters to keysyms', function () {
+ expect(keysyms.lookup('ø'.charCodeAt())).to.be.equal(0xf8);
+
+ expect(keysyms.lookup('é'.charCodeAt())).to.be.equal(0xe9);
+ });
+ it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function () {
+ expect(keysyms.lookup('Š'.charCodeAt())).to.be.equal(0x01a9);
+ });
+ it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function () {
+ expect(keysyms.lookup('ũ'.charCodeAt())).to.be.equal(0x03fd);
+ });
+ it('should map unknown codepoints to the Unicode range', function () {
+ expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a);
+ expect(keysyms.lookup('\u262D'.charCodeAt())).to.be.equal(0x100262d);
+ });
+ // This requires very recent versions of most browsers... skipping for now
+ it.skip('should map UCS-4 codepoints to the Unicode range', function () {
+ //expect(keysyms.lookup('\u{1F686}'.codePointAt())).to.be.equal(0x101f686);
+ });
+ });
+
+ describe('getKeycode', function () {
+ it('should pass through proper code', function () {
+ expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon');
+ });
+ it('should map legacy values', function () {
+ expect(KeyboardUtil.getKeycode({code: ''})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKeycode({code: 'OSLeft'})).to.be.equal('MetaLeft');
+ });
+ it('should map keyCode to code when possible', function () {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x14})).to.be.equal('CapsLock');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x5b})).to.be.equal('MetaLeft');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x35})).to.be.equal('Digit5');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x65})).to.be.equal('Numpad5');
+ });
+ it('should map keyCode left/right side', function () {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 1})).to.be.equal('ShiftLeft');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 2})).to.be.equal('ShiftRight');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 1})).to.be.equal('ControlLeft');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 2})).to.be.equal('ControlRight');
+ });
+ it('should map keyCode on numpad', function () {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 0})).to.be.equal('Enter');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 3})).to.be.equal('NumpadEnter');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 0})).to.be.equal('End');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 3})).to.be.equal('Numpad1');
+ });
+ it('should return Unidentified when it cannot map the keyCode', function () {
+ expect(KeyboardUtil.getKeycode({keycode: 0x42})).to.be.equal('Unidentified');
+ });
+
+ describe('Fix Meta on macOS', function () {
+ let origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.platform = "Mac x86_64";
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should respect ContextMenu on modern browser', function () {
+ expect(KeyboardUtil.getKeycode({code: 'ContextMenu', keyCode: 0x5d})).to.be.equal('ContextMenu');
+ });
+ it('should translate legacy ContextMenu to MetaRight', function () {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x5d})).to.be.equal('MetaRight');
+ });
+ });
+ });
+
+ describe('getKey', function () {
+ it('should prefer key', function () {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
+ });
+ it('should map legacy values', function () {
+ expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
+ expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
+ expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
+ expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
+ expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft');
+ });
+ it('should use code if no key', function () {
+ expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace');
+ });
+ it('should not use code fallback for character keys', function () {
+ expect(KeyboardUtil.getKey({code: 'KeyA'})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKey({code: 'Digit1'})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKey({code: 'Period'})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKey({code: 'Numpad1'})).to.be.equal('Unidentified');
+ });
+ it('should use charCode if no key', function () {
+ expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š');
+ });
+ it('should return Unidentified when it cannot map the key', function () {
+ expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');
+ });
+
+ describe('Broken key AltGraph on IE/Edge', function () {
+ let origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should ignore printable character key on IE', function () {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
+ expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
+ });
+ it('should ignore printable character key on Edge', function () {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
+ expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
+ });
+ it('should allow non-printable character key on IE', function () {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
+ expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
+ });
+ it('should allow non-printable character key on Edge', function () {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
+ expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
+ });
+ });
+ });
+
+ describe('getKeysym', function () {
+ describe('Non-character keys', function () {
+ it('should recognize the right keys', function () {
+ expect(KeyboardUtil.getKeysym({key: 'Enter'})).to.be.equal(0xFF0D);
+ expect(KeyboardUtil.getKeysym({key: 'Backspace'})).to.be.equal(0xFF08);
+ expect(KeyboardUtil.getKeysym({key: 'Tab'})).to.be.equal(0xFF09);
+ expect(KeyboardUtil.getKeysym({key: 'Shift'})).to.be.equal(0xFFE1);
+ expect(KeyboardUtil.getKeysym({key: 'Control'})).to.be.equal(0xFFE3);
+ expect(KeyboardUtil.getKeysym({key: 'Alt'})).to.be.equal(0xFFE9);
+ expect(KeyboardUtil.getKeysym({key: 'Meta'})).to.be.equal(0xFFEB);
+ expect(KeyboardUtil.getKeysym({key: 'Escape'})).to.be.equal(0xFF1B);
+ expect(KeyboardUtil.getKeysym({key: 'ArrowUp'})).to.be.equal(0xFF52);
+ });
+ it('should map left/right side', function () {
+ expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1);
+ expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2);
+ expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3);
+ expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4);
+ });
+ it('should handle AltGraph', function () {
+ expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA);
+ expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03);
+ });
+ it('should return null for unknown keys', function () {
+ expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null;
+ expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null;
+ });
+ it('should handle remappings', function () {
+ expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09);
+ });
+ });
+
+ describe('Numpad', function () {
+ it('should handle Numpad numbers', function () {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
+ expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
+ });
+ it('should handle Numpad non-character keys', function () {
+ expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50);
+ expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95);
+ expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF);
+ expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
+ });
+ it('should handle Numpad Decimal key', function () {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
+ expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
+ });
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/test.keyboard.js b/systemvm/agent/noVNC/tests/test.keyboard.js
new file mode 100644
index 0000000..77fe3f6
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.keyboard.js
@@ -0,0 +1,510 @@
+const expect = chai.expect;
+
+import Keyboard from '../core/input/keyboard.js';
+import * as browser from '../core/util/browser.js';
+
+describe('Key Event Handling', function () {
+ "use strict";
+
+ // The real KeyboardEvent constructor might not work everywhere we
+ // want to run these tests
+ function keyevent(typeArg, KeyboardEventInit) {
+ const e = { type: typeArg };
+ for (let key in KeyboardEventInit) {
+ e[key] = KeyboardEventInit[key];
+ }
+ e.stopPropagation = sinon.spy();
+ e.preventDefault = sinon.spy();
+ return e;
+ }
+
+ describe('Decode Keyboard Events', function () {
+ it('should decode keydown events', function (done) {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ });
+ it('should decode keyup events', function (done) {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ let calls = 0;
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ if (calls++ === 1) {
+ expect(down).to.be.equal(false);
+ done();
+ }
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
+ });
+
+ describe('Legacy keypress Events', function () {
+ it('should wait for keypress when needed', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+ it('should decode keypress events', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
+ });
+ it('should ignore keypress with different code', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+ it('should handle keypress with missing code', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
+ });
+ it('should guess key if no keypress and numeric key', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x32);
+ expect(code).to.be.equal('Digit2');
+ expect(down).to.be.equal(true);
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32}));
+ });
+ it('should guess key if no keypress and alpha key', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false}));
+ });
+ it('should guess key if no keypress and alpha key (with shift)', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x41);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true}));
+ });
+ it('should not guess key if no keypress and unknown key', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09}));
+ });
+ });
+
+ describe('suppress the right events at the right time', function () {
+ beforeEach(function () {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ });
+ it('should suppress anything with a valid key', function () {
+ const kbd = new Keyboard(document, {});
+ const evt1 = keyevent('keydown', {code: 'KeyA', key: 'a'});
+ kbd._handleKeyDown(evt1);
+ expect(evt1.preventDefault).to.have.been.called;
+ const evt2 = keyevent('keyup', {code: 'KeyA', key: 'a'});
+ kbd._handleKeyUp(evt2);
+ expect(evt2.preventDefault).to.have.been.called;
+ });
+ it('should not suppress keys without key', function () {
+ const kbd = new Keyboard(document, {});
+ const evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
+ kbd._handleKeyDown(evt);
+ expect(evt.preventDefault).to.not.have.been.called;
+ });
+ it('should suppress the following keypress event', function () {
+ const kbd = new Keyboard(document, {});
+ const evt1 = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
+ kbd._handleKeyDown(evt1);
+ const evt2 = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
+ kbd._handleKeyPress(evt2);
+ expect(evt2.preventDefault).to.have.been.called;
+ });
+ });
+ });
+
+ describe('Fake keyup', function () {
+ it('should fake keyup events for virtual keyboards', function (done) {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ let count = 0;
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ switch (count++) {
+ case 0:
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('Unidentified');
+ expect(down).to.be.equal(true);
+ break;
+ case 1:
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('Unidentified');
+ expect(down).to.be.equal(false);
+ done();
+ }
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'}));
+ });
+
+ describe('iOS', function () {
+ let origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.platform = "iPhone 9.0";
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should fake keyup events on iOS', function (done) {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ let count = 0;
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ switch (count++) {
+ case 0:
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ break;
+ case 1:
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(false);
+ done();
+ }
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ });
+ });
+ });
+
+ describe('Track Key State', function () {
+ beforeEach(function () {
+ if (browser.isIE() || browser.isEdge()) this.skip();
+ });
+ it('should send release using the same keysym as the press', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ if (!down) {
+ done();
+ }
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
+ });
+ it('should send the same keysym for multiple presses', function () {
+ let count = 0;
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ count++;
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
+ expect(count).to.be.equal(2);
+ });
+ it('should do nothing on keyup events if no keys are down', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+
+ describe('Legacy Events', function () {
+ it('should track keys using keyCode if no code', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('Platform65');
+ if (!down) {
+ done();
+ }
+ };
+ kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));
+ kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));
+ });
+ it('should ignore compositing code', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('Unidentified');
+ };
+ kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'}));
+ });
+ it('should track keys using keyIdentifier if no code', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('Platform65');
+ if (!down) {
+ done();
+ }
+ };
+ kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'}));
+ kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'}));
+ });
+ });
+ });
+
+ describe('Shuffle modifiers on macOS', function () {
+ let origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.platform = "Mac x86_64";
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should change Alt to AltGraph', function () {
+ let count = 0;
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ switch (count++) {
+ case 0:
+ expect(keysym).to.be.equal(0xFF7E);
+ expect(code).to.be.equal('AltLeft');
+ break;
+ case 1:
+ expect(keysym).to.be.equal(0xFE03);
+ expect(code).to.be.equal('AltRight');
+ break;
+ }
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
+ expect(count).to.be.equal(2);
+ });
+ it('should change left Super to Alt', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0xFFE9);
+ expect(code).to.be.equal('MetaLeft');
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
+ });
+ it('should change right Super to left Super', function (done) {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = (keysym, code, down) => {
+ expect(keysym).to.be.equal(0xFFEB);
+ expect(code).to.be.equal('MetaRight');
+ done();
+ };
+ kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
+ });
+ });
+
+ describe('Escape AltGraph on Windows', function () {
+ let origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.platform = "Windows x86_64";
+
+ this.clock = sinon.useFakeTimers();
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ this.clock.restore();
+ });
+
+ it('should supress ControlLeft until it knows if it is AltGr', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+
+ it('should not trigger on repeating ControlLeft', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ expect(kbd.onkeyevent).to.have.been.calledTwice;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
+ });
+
+ it('should not supress ControlRight', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlRight', key: 'Control', location: 2}));
+ expect(kbd.onkeyevent).to.have.been.calledOnce;
+ expect(kbd.onkeyevent).to.have.been.calledWith(0xffe4, "ControlRight", true);
+ });
+
+ it('should release ControlLeft after 100 ms', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ this.clock.tick(100);
+ expect(kbd.onkeyevent).to.have.been.calledOnce;
+ expect(kbd.onkeyevent).to.have.been.calledWith(0xffe3, "ControlLeft", true);
+ });
+
+ it('should release ControlLeft on other key press', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ expect(kbd.onkeyevent).to.have.been.calledTwice;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, "KeyA", true);
+
+ // Check that the timer is properly dead
+ kbd.onkeyevent.reset();
+ this.clock.tick(100);
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+
+ it('should release ControlLeft on other key release', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ expect(kbd.onkeyevent).to.have.been.calledOnce;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x61, "KeyA", true);
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
+ expect(kbd.onkeyevent).to.have.been.calledThrice;
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
+ expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, "KeyA", false);
+
+ // Check that the timer is properly dead
+ kbd.onkeyevent.reset();
+ this.clock.tick(100);
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+
+ it('should generate AltGraph for quick Ctrl+Alt sequence', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
+ this.clock.tick(20);
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
+ expect(kbd.onkeyevent).to.have.been.calledOnce;
+ expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
+
+ // Check that the timer is properly dead
+ kbd.onkeyevent.reset();
+ this.clock.tick(100);
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+
+ it('should generate Ctrl, Alt for slow Ctrl+Alt sequence', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
+ this.clock.tick(60);
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
+ expect(kbd.onkeyevent).to.have.been.calledTwice;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, "AltRight", true);
+
+ // Check that the timer is properly dead
+ kbd.onkeyevent.reset();
+ this.clock.tick(100);
+ expect(kbd.onkeyevent).to.not.have.been.called;
+ });
+
+ it('should pass through single Alt', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
+ expect(kbd.onkeyevent).to.have.been.calledOnce;
+ expect(kbd.onkeyevent).to.have.been.calledWith(0xffea, 'AltRight', true);
+ });
+
+ it('should pass through single AltGr', function () {
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2}));
+ expect(kbd.onkeyevent).to.have.been.calledOnce;
+ expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/test.localization.js b/systemvm/agent/noVNC/tests/test.localization.js
new file mode 100644
index 0000000..9570c17
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.localization.js
@@ -0,0 +1,72 @@
+const expect = chai.expect;
+import { l10n } from '../app/localization.js';
+
+describe('Localization', function () {
+ "use strict";
+
+ describe('language selection', function () {
+ let origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.languages !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.languages = [];
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should use English by default', function () {
+ expect(l10n.language).to.equal('en');
+ });
+ it('should use English if no user language matches', function () {
+ window.navigator.languages = ["nl", "de"];
+ l10n.setup(["es", "fr"]);
+ expect(l10n.language).to.equal('en');
+ });
+ it('should use the most preferred user language', function () {
+ window.navigator.languages = ["nl", "de", "fr"];
+ l10n.setup(["es", "fr", "de"]);
+ expect(l10n.language).to.equal('de');
+ });
+ it('should prefer sub-languages languages', function () {
+ window.navigator.languages = ["pt-BR"];
+ l10n.setup(["pt", "pt-BR"]);
+ expect(l10n.language).to.equal('pt-BR');
+ });
+ it('should fall back to language "parents"', function () {
+ window.navigator.languages = ["pt-BR"];
+ l10n.setup(["fr", "pt", "de"]);
+ expect(l10n.language).to.equal('pt');
+ });
+ it('should not use specific language when user asks for a generic language', function () {
+ window.navigator.languages = ["pt", "de"];
+ l10n.setup(["fr", "pt-BR", "de"]);
+ expect(l10n.language).to.equal('de');
+ });
+ it('should handle underscore as a separator', function () {
+ window.navigator.languages = ["pt-BR"];
+ l10n.setup(["pt_BR"]);
+ expect(l10n.language).to.equal('pt_BR');
+ });
+ it('should handle difference in case', function () {
+ window.navigator.languages = ["pt-br"];
+ l10n.setup(["pt-BR"]);
+ expect(l10n.language).to.equal('pt-BR');
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/test.mouse.js b/systemvm/agent/noVNC/tests/test.mouse.js
new file mode 100644
index 0000000..78c74f1
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.mouse.js
@@ -0,0 +1,304 @@
+const expect = chai.expect;
+
+import Mouse from '../core/input/mouse.js';
+
+describe('Mouse Event Handling', function () {
+ "use strict";
+
+ let target;
+
+ beforeEach(function () {
+ // For these tests we can assume that the canvas is 100x100
+ // located at coordinates 10x10
+ target = document.createElement('canvas');
+ target.style.position = "absolute";
+ target.style.top = "10px";
+ target.style.left = "10px";
+ target.style.width = "100px";
+ target.style.height = "100px";
+ document.body.appendChild(target);
+ });
+ afterEach(function () {
+ document.body.removeChild(target);
+ target = null;
+ });
+
+ // The real constructors might not work everywhere we
+ // want to run these tests
+ const mouseevent = (typeArg, MouseEventInit) => {
+ const e = { type: typeArg };
+ for (let key in MouseEventInit) {
+ e[key] = MouseEventInit[key];
+ }
+ e.stopPropagation = sinon.spy();
+ e.preventDefault = sinon.spy();
+ return e;
+ };
+ const touchevent = mouseevent;
+
+ describe('Decode Mouse Events', function () {
+ it('should decode mousedown events', function (done) {
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = (x, y, down, bmask) => {
+ expect(bmask).to.be.equal(0x01);
+ expect(down).to.be.equal(1);
+ done();
+ };
+ mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
+ });
+ it('should decode mouseup events', function (done) {
+ let calls = 0;
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = (x, y, down, bmask) => {
+ expect(bmask).to.be.equal(0x01);
+ if (calls++ === 1) {
+ expect(down).to.not.be.equal(1);
+ done();
+ }
+ };
+ mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
+ mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' }));
+ });
+ it('should decode mousemove events', function (done) {
+ const mouse = new Mouse(target);
+ mouse.onmousemove = (x, y) => {
+ // Note that target relative coordinates are sent
+ expect(x).to.be.equal(40);
+ expect(y).to.be.equal(10);
+ done();
+ };
+ mouse._handleMouseMove(mouseevent('mousemove',
+ { clientX: 50, clientY: 20 }));
+ });
+ it('should decode mousewheel events', function (done) {
+ let calls = 0;
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = (x, y, down, bmask) => {
+ calls++;
+ expect(bmask).to.be.equal(1<<6);
+ if (calls === 1) {
+ expect(down).to.be.equal(1);
+ } else if (calls === 2) {
+ expect(down).to.not.be.equal(1);
+ done();
+ }
+ };
+ mouse._handleMouseWheel(mouseevent('mousewheel',
+ { deltaX: 50, deltaY: 0,
+ deltaMode: 0}));
+ });
+ });
+
+ describe('Double-click for Touch', function () {
+
+ beforeEach(function () { this.clock = sinon.useFakeTimers(); });
+ afterEach(function () { this.clock.restore(); });
+
+ it('should use same pos for 2nd tap if close enough', function (done) {
+ let calls = 0;
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = (x, y, down, bmask) => {
+ calls++;
+ if (calls === 1) {
+ expect(down).to.be.equal(1);
+ expect(x).to.be.equal(68);
+ expect(y).to.be.equal(36);
+ } else if (calls === 3) {
+ expect(down).to.be.equal(1);
+ expect(x).to.be.equal(68);
+ expect(y).to.be.equal(36);
+ done();
+ }
+ };
+ // touch events are sent in an array of events
+ // with one item for each touch point
+ mouse._handleMouseDown(touchevent(
+ 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
+ this.clock.tick(10);
+ mouse._handleMouseUp(touchevent(
+ 'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
+ this.clock.tick(200);
+ mouse._handleMouseDown(touchevent(
+ 'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
+ this.clock.tick(10);
+ mouse._handleMouseUp(touchevent(
+ 'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
+ });
+
+ it('should not modify 2nd tap pos if far apart', function (done) {
+ let calls = 0;
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = (x, y, down, bmask) => {
+ calls++;
+ if (calls === 1) {
+ expect(down).to.be.equal(1);
+ expect(x).to.be.equal(68);
+ expect(y).to.be.equal(36);
+ } else if (calls === 3) {
+ expect(down).to.be.equal(1);
+ expect(x).to.not.be.equal(68);
+ expect(y).to.not.be.equal(36);
+ done();
+ }
+ };
+ mouse._handleMouseDown(touchevent(
+ 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
+ this.clock.tick(10);
+ mouse._handleMouseUp(touchevent(
+ 'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
+ this.clock.tick(200);
+ mouse._handleMouseDown(touchevent(
+ 'touchstart', { touches: [{ clientX: 57, clientY: 35 }]}));
+ this.clock.tick(10);
+ mouse._handleMouseUp(touchevent(
+ 'touchend', { touches: [{ clientX: 56, clientY: 36 }]}));
+ });
+
+ it('should not modify 2nd tap pos if not soon enough', function (done) {
+ let calls = 0;
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = (x, y, down, bmask) => {
+ calls++;
+ if (calls === 1) {
+ expect(down).to.be.equal(1);
+ expect(x).to.be.equal(68);
+ expect(y).to.be.equal(36);
+ } else if (calls === 3) {
+ expect(down).to.be.equal(1);
+ expect(x).to.not.be.equal(68);
+ expect(y).to.not.be.equal(36);
+ done();
+ }
+ };
+ mouse._handleMouseDown(touchevent(
+ 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
+ this.clock.tick(10);
+ mouse._handleMouseUp(touchevent(
+ 'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
+ this.clock.tick(500);
+ mouse._handleMouseDown(touchevent(
+ 'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
+ this.clock.tick(10);
+ mouse._handleMouseUp(touchevent(
+ 'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
+ });
+
+ it('should not modify 2nd tap pos if not touch', function (done) {
+ let calls = 0;
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = (x, y, down, bmask) => {
+ calls++;
+ if (calls === 1) {
+ expect(down).to.be.equal(1);
+ expect(x).to.be.equal(68);
+ expect(y).to.be.equal(36);
+ } else if (calls === 3) {
+ expect(down).to.be.equal(1);
+ expect(x).to.not.be.equal(68);
+ expect(y).to.not.be.equal(36);
+ done();
+ }
+ };
+ mouse._handleMouseDown(mouseevent(
+ 'mousedown', { button: '0x01', clientX: 78, clientY: 46 }));
+ this.clock.tick(10);
+ mouse._handleMouseUp(mouseevent(
+ 'mouseup', { button: '0x01', clientX: 79, clientY: 45 }));
+ this.clock.tick(200);
+ mouse._handleMouseDown(mouseevent(
+ 'mousedown', { button: '0x01', clientX: 67, clientY: 35 }));
+ this.clock.tick(10);
+ mouse._handleMouseUp(mouseevent(
+ 'mouseup', { button: '0x01', clientX: 66, clientY: 36 }));
+ });
+
+ });
+
+ describe('Accumulate mouse wheel events with small delta', function () {
+
+ beforeEach(function () { this.clock = sinon.useFakeTimers(); });
+ afterEach(function () { this.clock.restore(); });
+
+ it('should accumulate wheel events if small enough', function () {
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = sinon.spy();
+
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 4, deltaY: 0, deltaMode: 0 }));
+ this.clock.tick(10);
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 4, deltaY: 0, deltaMode: 0 }));
+
+ // threshold is 10
+ expect(mouse._accumulatedWheelDeltaX).to.be.equal(8);
+
+ this.clock.tick(10);
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 4, deltaY: 0, deltaMode: 0 }));
+
+ expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
+
+ this.clock.tick(10);
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 4, deltaY: 9, deltaMode: 0 }));
+
+ expect(mouse._accumulatedWheelDeltaX).to.be.equal(4);
+ expect(mouse._accumulatedWheelDeltaY).to.be.equal(9);
+
+ expect(mouse.onmousebutton).to.have.callCount(2); // still
+ });
+
+ it('should not accumulate large wheel events', function () {
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = sinon.spy();
+
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 11, deltaY: 0, deltaMode: 0 }));
+ this.clock.tick(10);
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 0, deltaY: 70, deltaMode: 0 }));
+ this.clock.tick(10);
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 400, deltaY: 400, deltaMode: 0 }));
+
+ expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up
+ });
+
+ it('should send even small wheel events after a timeout', function () {
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = sinon.spy();
+
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 1, deltaY: 0, deltaMode: 0 }));
+ this.clock.tick(51); // timeout on 50 ms
+
+ expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
+ });
+
+ it('should account for non-zero deltaMode', function () {
+ const mouse = new Mouse(target);
+ mouse.onmousebutton = sinon.spy();
+
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 0, deltaY: 2, deltaMode: 1 }));
+
+ this.clock.tick(10);
+
+ mouse._handleMouseWheel(mouseevent(
+ 'mousewheel', { clientX: 18, clientY: 40,
+ deltaX: 1, deltaY: 0, deltaMode: 2 }));
+
+ expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up
+ });
+ });
+
+});
diff --git a/systemvm/agent/noVNC/tests/test.rfb.js b/systemvm/agent/noVNC/tests/test.rfb.js
new file mode 100644
index 0000000..99c9c90
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.rfb.js
@@ -0,0 +1,2389 @@
+const expect = chai.expect;
+
+import RFB from '../core/rfb.js';
+import Websock from '../core/websock.js';
+import { encodings } from '../core/encodings.js';
+
+import FakeWebSocket from './fake.websocket.js';
+
+/* UIEvent constructor polyfill for IE */
+(() => {
+ if (typeof window.UIEvent === "function") return;
+
+ function UIEvent( event, params ) {
+ params = params || { bubbles: false, cancelable: false, view: window, detail: undefined };
+ const evt = document.createEvent( 'UIEvent' );
+ evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail );
+ return evt;
+ }
+
+ UIEvent.prototype = window.UIEvent.prototype;
+
+ window.UIEvent = UIEvent;
+})();
+
+function push8(arr, num) {
+ "use strict";
+ arr.push(num & 0xFF);
+}
+
+function push16(arr, num) {
+ "use strict";
+ arr.push((num >> 8) & 0xFF,
+ num & 0xFF);
+}
+
+function push32(arr, num) {
+ "use strict";
+ arr.push((num >> 24) & 0xFF,
+ (num >> 16) & 0xFF,
+ (num >> 8) & 0xFF,
+ num & 0xFF);
+}
+
+describe('Remote Frame Buffer Protocol Client', function () {
+ let clock;
+ let raf;
+
+ before(FakeWebSocket.replace);
+ after(FakeWebSocket.restore);
+
+ before(function () {
+ this.clock = clock = sinon.useFakeTimers();
+ // sinon doesn't support this yet
+ raf = window.requestAnimationFrame;
+ window.requestAnimationFrame = setTimeout;
+ // Use a single set of buffers instead of reallocating to
+ // speed up tests
+ const sock = new Websock();
+ const _sQ = new Uint8Array(sock._sQbufferSize);
+ const rQ = new Uint8Array(sock._rQbufferSize);
+
+ Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers;
+ Websock.prototype._allocate_buffers = function () {
+ this._sQ = _sQ;
+ this._rQ = rQ;
+ };
+
+ });
+
+ after(function () {
+ Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers;
+ this.clock.restore();
+ window.requestAnimationFrame = raf;
+ });
+
+ let container;
+ let rfbs;
+
+ beforeEach(function () {
+ // Create a container element for all RFB objects to attach to
+ container = document.createElement('div');
+ container.style.width = "100%";
+ container.style.height = "100%";
+ document.body.appendChild(container);
+
+ // And track all created RFB objects
+ rfbs = [];
+ });
+ afterEach(function () {
+ // Make sure every created RFB object is properly cleaned up
+ // or they might affect subsequent tests
+ rfbs.forEach(function (rfb) {
+ rfb.disconnect();
+ expect(rfb._disconnect).to.have.been.called;
+ });
+ rfbs = [];
+
+ document.body.removeChild(container);
+ container = null;
+ });
+
+ function make_rfb(url, options) {
+ url = url || 'wss://host:8675';
+ const rfb = new RFB(container, url, options);
+ clock.tick();
+ rfb._sock._websocket._open();
+ rfb._rfb_connection_state = 'connected';
+ sinon.spy(rfb, "_disconnect");
+ rfbs.push(rfb);
+ return rfb;
+ }
+
+ describe('Connecting/Disconnecting', function () {
+ describe('#RFB', function () {
+ it('should set the current state to "connecting"', function () {
+ const client = new RFB(document.createElement('div'), 'wss://host:8675');
+ client._rfb_connection_state = '';
+ this.clock.tick();
+ expect(client._rfb_connection_state).to.equal('connecting');
+ });
+
+ it('should actually connect to the websocket', function () {
+ const client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
+ sinon.spy(client._sock, 'open');
+ this.clock.tick();
+ expect(client._sock.open).to.have.been.calledOnce;
+ expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH');
+ });
+ });
+
+ describe('#disconnect', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ });
+
+ it('should go to state "disconnecting" before "disconnected"', function () {
+ sinon.spy(client, '_updateConnectionState');
+ client.disconnect();
+ expect(client._updateConnectionState).to.have.been.calledTwice;
+ expect(client._updateConnectionState.getCall(0).args[0])
+ .to.equal('disconnecting');
+ expect(client._updateConnectionState.getCall(1).args[0])
+ .to.equal('disconnected');
+ expect(client._rfb_connection_state).to.equal('disconnected');
+ });
+
+ it('should unregister error event handler', function () {
+ sinon.spy(client._sock, 'off');
+ client.disconnect();
+ expect(client._sock.off).to.have.been.calledWith('error');
+ });
+
+ it('should unregister message event handler', function () {
+ sinon.spy(client._sock, 'off');
+ client.disconnect();
+ expect(client._sock.off).to.have.been.calledWith('message');
+ });
+
+ it('should unregister open event handler', function () {
+ sinon.spy(client._sock, 'off');
+ client.disconnect();
+ expect(client._sock.off).to.have.been.calledWith('open');
+ });
+ });
+
+ describe('#sendCredentials', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ client._rfb_connection_state = 'connecting';
+ });
+
+ it('should set the rfb credentials properly"', function () {
+ client.sendCredentials({ password: 'pass' });
+ expect(client._rfb_credentials).to.deep.equal({ password: 'pass' });
+ });
+
+ it('should call init_msg "soon"', function () {
+ client._init_msg = sinon.spy();
+ client.sendCredentials({ password: 'pass' });
+ this.clock.tick(5);
+ expect(client._init_msg).to.have.been.calledOnce;
+ });
+ });
+ });
+
+ describe('Public API Basic Behavior', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ });
+
+ describe('#sendCtrlAlDel', function () {
+ it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
+ const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: () => {}};
+ RFB.messages.keyEvent(expected, 0xFFE3, 1);
+ RFB.messages.keyEvent(expected, 0xFFE9, 1);
+ RFB.messages.keyEvent(expected, 0xFFFF, 1);
+ RFB.messages.keyEvent(expected, 0xFFFF, 0);
+ RFB.messages.keyEvent(expected, 0xFFE9, 0);
+ RFB.messages.keyEvent(expected, 0xFFE3, 0);
+
+ client.sendCtrlAltDel();
+ expect(client._sock).to.have.sent(expected._sQ);
+ });
+
+ it('should not send the keys if we are not in a normal state', function () {
+ sinon.spy(client._sock, 'flush');
+ client._rfb_connection_state = "connecting";
+ client.sendCtrlAltDel();
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+
+ it('should not send the keys if we are set as view_only', function () {
+ sinon.spy(client._sock, 'flush');
+ client._viewOnly = true;
+ client.sendCtrlAltDel();
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+ });
+
+ describe('#sendKey', function () {
+ it('should send a single key with the given code and state (down = true)', function () {
+ const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
+ RFB.messages.keyEvent(expected, 123, 1);
+ client.sendKey(123, 'Key123', true);
+ expect(client._sock).to.have.sent(expected._sQ);
+ });
+
+ it('should send both a down and up event if the state is not specified', function () {
+ const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
+ RFB.messages.keyEvent(expected, 123, 1);
+ RFB.messages.keyEvent(expected, 123, 0);
+ client.sendKey(123, 'Key123');
+ expect(client._sock).to.have.sent(expected._sQ);
+ });
+
+ it('should not send the key if we are not in a normal state', function () {
+ sinon.spy(client._sock, 'flush');
+ client._rfb_connection_state = "connecting";
+ client.sendKey(123, 'Key123');
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+
+ it('should not send the key if we are set as view_only', function () {
+ sinon.spy(client._sock, 'flush');
+ client._viewOnly = true;
+ client.sendKey(123, 'Key123');
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+
+ it('should send QEMU extended events if supported', function () {
+ client._qemuExtKeyEventSupported = true;
+ const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
+ RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039);
+ client.sendKey(0x20, 'Space', true);
+ expect(client._sock).to.have.sent(expected._sQ);
+ });
+
+ it('should not send QEMU extended events if unknown key code', function () {
+ client._qemuExtKeyEventSupported = true;
+ const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
+ RFB.messages.keyEvent(expected, 123, 1);
+ client.sendKey(123, 'FooBar', true);
+ expect(client._sock).to.have.sent(expected._sQ);
+ });
+ });
+
+ describe('#focus', function () {
+ it('should move focus to canvas object', function () {
+ client._canvas.focus = sinon.spy();
+ client.focus();
+ expect(client._canvas.focus).to.have.been.called.once;
+ });
+ });
+
+ describe('#blur', function () {
+ it('should remove focus from canvas object', function () {
+ client._canvas.blur = sinon.spy();
+ client.blur();
+ expect(client._canvas.blur).to.have.been.called.once;
+ });
+ });
+
+ describe('#clipboardPasteFrom', function () {
+ it('should send the given text in a paste event', function () {
+ const expected = {_sQ: new Uint8Array(11), _sQlen: 0,
+ _sQbufferSize: 11, flush: () => {}};
+ RFB.messages.clientCutText(expected, 'abc');
+ client.clipboardPasteFrom('abc');
+ expect(client._sock).to.have.sent(expected._sQ);
+ });
+
+ it('should flush multiple times for large clipboards', function () {
+ sinon.spy(client._sock, 'flush');
+ let long_text = "";
+ for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {
+ long_text += 'a';
+ }
+ client.clipboardPasteFrom(long_text);
+ expect(client._sock.flush).to.have.been.calledTwice;
+ });
+
+ it('should not send the text if we are not in a normal state', function () {
+ sinon.spy(client._sock, 'flush');
+ client._rfb_connection_state = "connecting";
+ client.clipboardPasteFrom('abc');
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+ });
+
+ describe("XVP operations", function () {
+ beforeEach(function () {
+ client._rfb_xvp_ver = 1;
+ });
+
+ it('should send the shutdown signal on #machineShutdown', function () {
+ client.machineShutdown();
+ expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
+ });
+
+ it('should send the reboot signal on #machineReboot', function () {
+ client.machineReboot();
+ expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
+ });
+
+ it('should send the reset signal on #machineReset', function () {
+ client.machineReset();
+ expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
+ });
+
+ it('should not send XVP operations with higher versions than we support', function () {
+ sinon.spy(client._sock, 'flush');
+ client._xvpOp(2, 7);
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+ });
+ });
+
+ describe('Clipping', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ container.style.width = '70px';
+ container.style.height = '80px';
+ client.clipViewport = true;
+ });
+
+ it('should update display clip state when changing the property', function () {
+ const spy = sinon.spy(client._display, "clipViewport", ["set"]);
+
+ client.clipViewport = false;
+ expect(spy.set).to.have.been.calledOnce;
+ expect(spy.set).to.have.been.calledWith(false);
+ spy.set.reset();
+
+ client.clipViewport = true;
+ expect(spy.set).to.have.been.calledOnce;
+ expect(spy.set).to.have.been.calledWith(true);
+ });
+
+ it('should update the viewport when the container size changes', function () {
+ sinon.spy(client._display, "viewportChangeSize");
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick();
+
+ expect(client._display.viewportChangeSize).to.have.been.calledOnce;
+ expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50);
+ });
+
+ it('should update the viewport when the remote session resizes', function () {
+ // Simple ExtendedDesktopSize FBU message
+ const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0x00 ];
+
+ sinon.spy(client._display, "viewportChangeSize");
+
+ client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+ // FIXME: Display implicitly calls viewportChangeSize() when
+ // resizing the framebuffer, hence calledTwice.
+ expect(client._display.viewportChangeSize).to.have.been.calledTwice;
+ expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80);
+ });
+
+ it('should not update the viewport if not clipping', function () {
+ client.clipViewport = false;
+ sinon.spy(client._display, "viewportChangeSize");
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick();
+
+ expect(client._display.viewportChangeSize).to.not.have.been.called;
+ });
+
+ it('should not update the viewport if scaling', function () {
+ client.scaleViewport = true;
+ sinon.spy(client._display, "viewportChangeSize");
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick();
+
+ expect(client._display.viewportChangeSize).to.not.have.been.called;
+ });
+
+ describe('Dragging', function () {
+ beforeEach(function () {
+ client.dragViewport = true;
+ sinon.spy(RFB.messages, "pointerEvent");
+ });
+
+ afterEach(function () {
+ RFB.messages.pointerEvent.restore();
+ });
+
+ it('should not send button messages when initiating viewport dragging', function () {
+ client._handleMouseButton(13, 9, 0x001);
+ expect(RFB.messages.pointerEvent).to.not.have.been.called;
+ });
+
+ it('should send button messages when release without movement', function () {
+ // Just up and down
+ client._handleMouseButton(13, 9, 0x001);
+ client._handleMouseButton(13, 9, 0x000);
+ expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
+
+ RFB.messages.pointerEvent.reset();
+
+ // Small movement
+ client._handleMouseButton(13, 9, 0x001);
+ client._handleMouseMove(15, 14);
+ client._handleMouseButton(15, 14, 0x000);
+ expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
+ });
+
+ it('should send button message directly when drag is disabled', function () {
+ client.dragViewport = false;
+ client._handleMouseButton(13, 9, 0x001);
+ expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
+ });
+
+ it('should be initiate viewport dragging on sufficient movement', function () {
+ sinon.spy(client._display, "viewportChangePos");
+
+ // Too small movement
+
+ client._handleMouseButton(13, 9, 0x001);
+ client._handleMouseMove(18, 9);
+
+ expect(RFB.messages.pointerEvent).to.not.have.been.called;
+ expect(client._display.viewportChangePos).to.not.have.been.called;
+
+ // Sufficient movement
+
+ client._handleMouseMove(43, 9);
+
+ expect(RFB.messages.pointerEvent).to.not.have.been.called;
+ expect(client._display.viewportChangePos).to.have.been.calledOnce;
+ expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);
+
+ client._display.viewportChangePos.reset();
+
+ // Now a small movement should move right away
+
+ client._handleMouseMove(43, 14);
+
+ expect(RFB.messages.pointerEvent).to.not.have.been.called;
+ expect(client._display.viewportChangePos).to.have.been.calledOnce;
+ expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);
+ });
+
+ it('should not send button messages when dragging ends', function () {
+ // First the movement
+
+ client._handleMouseButton(13, 9, 0x001);
+ client._handleMouseMove(43, 9);
+ client._handleMouseButton(43, 9, 0x000);
+
+ expect(RFB.messages.pointerEvent).to.not.have.been.called;
+ });
+
+ it('should terminate viewport dragging on a button up event', function () {
+ // First the dragging movement
+
+ client._handleMouseButton(13, 9, 0x001);
+ client._handleMouseMove(43, 9);
+ client._handleMouseButton(43, 9, 0x000);
+
+ // Another movement now should not move the viewport
+
+ sinon.spy(client._display, "viewportChangePos");
+
+ client._handleMouseMove(43, 59);
+
+ expect(client._display.viewportChangePos).to.not.have.been.called;
+ });
+ });
+ });
+
+ describe('Scaling', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ container.style.width = '70px';
+ container.style.height = '80px';
+ client.scaleViewport = true;
+ });
+
+ it('should update display scale factor when changing the property', function () {
+ const spy = sinon.spy(client._display, "scale", ["set"]);
+ sinon.spy(client._display, "autoscale");
+
+ client.scaleViewport = false;
+ expect(spy.set).to.have.been.calledOnce;
+ expect(spy.set).to.have.been.calledWith(1.0);
+ expect(client._display.autoscale).to.not.have.been.called;
+
+ client.scaleViewport = true;
+ expect(client._display.autoscale).to.have.been.calledOnce;
+ expect(client._display.autoscale).to.have.been.calledWith(70, 80);
+ });
+
+ it('should update the clipping setting when changing the property', function () {
+ client.clipViewport = true;
+
+ const spy = sinon.spy(client._display, "clipViewport", ["set"]);
+
+ client.scaleViewport = false;
+ expect(spy.set).to.have.been.calledOnce;
+ expect(spy.set).to.have.been.calledWith(true);
+
+ spy.set.reset();
+
+ client.scaleViewport = true;
+ expect(spy.set).to.have.been.calledOnce;
+ expect(spy.set).to.have.been.calledWith(false);
+ });
+
+ it('should update the scaling when the container size changes', function () {
+ sinon.spy(client._display, "autoscale");
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick();
+
+ expect(client._display.autoscale).to.have.been.calledOnce;
+ expect(client._display.autoscale).to.have.been.calledWith(40, 50);
+ });
+
+ it('should update the scaling when the remote session resizes', function () {
+ // Simple ExtendedDesktopSize FBU message
+ const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0x00 ];
+
+ sinon.spy(client._display, "autoscale");
+
+ client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+ expect(client._display.autoscale).to.have.been.calledOnce;
+ expect(client._display.autoscale).to.have.been.calledWith(70, 80);
+ });
+
+ it('should not update the display scale factor if not scaling', function () {
+ client.scaleViewport = false;
+
+ sinon.spy(client._display, "autoscale");
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick();
+
+ expect(client._display.autoscale).to.not.have.been.called;
+ });
+ });
+
+ describe('Remote resize', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ client._supportsSetDesktopSize = true;
+ client.resizeSession = true;
+ container.style.width = '70px';
+ container.style.height = '80px';
+ sinon.spy(RFB.messages, "setDesktopSize");
+ });
+
+ afterEach(function () {
+ RFB.messages.setDesktopSize.restore();
+ });
+
+ it('should only request a resize when turned on', function () {
+ client.resizeSession = false;
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+ client.resizeSession = true;
+ expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+ });
+
+ it('should request a resize when initially connecting', function () {
+ // Simple ExtendedDesktopSize FBU message
+ const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00 ];
+
+ // First message should trigger a resize
+
+ client._supportsSetDesktopSize = false;
+
+ client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+ expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+ expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0);
+
+ RFB.messages.setDesktopSize.reset();
+
+ // Second message should not trigger a resize
+
+ client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+ });
+
+ it('should request a resize when the container resizes', function () {
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick(1000);
+
+ expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+ expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
+ });
+
+ it('should not resize until the container size is stable', function () {
+ container.style.width = '20px';
+ container.style.height = '30px';
+ const event1 = new UIEvent('resize');
+ window.dispatchEvent(event1);
+ clock.tick(400);
+
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event2 = new UIEvent('resize');
+ window.dispatchEvent(event2);
+ clock.tick(400);
+
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+
+ clock.tick(200);
+
+ expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+ expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
+ });
+
+ it('should not resize when resize is disabled', function () {
+ client._resizeSession = false;
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick(1000);
+
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+ });
+
+ it('should not resize when resize is not supported', function () {
+ client._supportsSetDesktopSize = false;
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick(1000);
+
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+ });
+
+ it('should not resize when in view only mode', function () {
+ client._viewOnly = true;
+
+ container.style.width = '40px';
+ container.style.height = '50px';
+ const event = new UIEvent('resize');
+ window.dispatchEvent(event);
+ clock.tick(1000);
+
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+ });
+
+ it('should not try to override a server resize', function () {
+ // Simple ExtendedDesktopSize FBU message
+ const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00 ];
+
+ client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+ expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+ });
+ });
+
+ describe('Misc Internals', function () {
+ describe('#_updateConnectionState', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ });
+
+ it('should clear the disconnect timer if the state is not "disconnecting"', function () {
+ const spy = sinon.spy();
+ client._disconnTimer = setTimeout(spy, 50);
+ client._rfb_connection_state = 'connecting';
+ client._updateConnectionState('connected');
+ this.clock.tick(51);
+ expect(spy).to.not.have.been.called;
+ expect(client._disconnTimer).to.be.null;
+ });
+
+ it('should set the rfb_connection_state', function () {
+ client._rfb_connection_state = 'connecting';
+ client._updateConnectionState('connected');
+ expect(client._rfb_connection_state).to.equal('connected');
+ });
+
+ it('should not change the state when we are disconnected', function () {
+ client.disconnect();
+ expect(client._rfb_connection_state).to.equal('disconnected');
+ client._updateConnectionState('connecting');
+ expect(client._rfb_connection_state).to.not.equal('connecting');
+ });
+
+ it('should ignore state changes to the same state', function () {
+ const connectSpy = sinon.spy();
+ client.addEventListener("connect", connectSpy);
+
+ expect(client._rfb_connection_state).to.equal('connected');
+ client._updateConnectionState('connected');
+ expect(connectSpy).to.not.have.been.called;
+
+ client.disconnect();
+
+ const disconnectSpy = sinon.spy();
+ client.addEventListener("disconnect", disconnectSpy);
+
+ expect(client._rfb_connection_state).to.equal('disconnected');
+ client._updateConnectionState('disconnected');
+ expect(disconnectSpy).to.not.have.been.called;
+ });
+
+ it('should ignore illegal state changes', function () {
+ const spy = sinon.spy();
+ client.addEventListener("disconnect", spy);
+ client._updateConnectionState('disconnected');
+ expect(client._rfb_connection_state).to.not.equal('disconnected');
+ expect(spy).to.not.have.been.called;
+ });
+ });
+
+ describe('#_fail', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ });
+
+ it('should close the WebSocket connection', function () {
+ sinon.spy(client._sock, 'close');
+ client._fail();
+ expect(client._sock.close).to.have.been.calledOnce;
+ });
+
+ it('should transition to disconnected', function () {
+ sinon.spy(client, '_updateConnectionState');
+ client._fail();
+ this.clock.tick(2000);
+ expect(client._updateConnectionState).to.have.been.called;
+ expect(client._rfb_connection_state).to.equal('disconnected');
+ });
+
+ it('should set clean_disconnect variable', function () {
+ client._rfb_clean_disconnect = true;
+ client._rfb_connection_state = 'connected';
+ client._fail();
+ expect(client._rfb_clean_disconnect).to.be.false;
+ });
+
+ it('should result in disconnect event with clean set to false', function () {
+ client._rfb_connection_state = 'connected';
+ const spy = sinon.spy();
+ client.addEventListener("disconnect", spy);
+ client._fail();
+ this.clock.tick(2000);
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.clean).to.be.false;
+ });
+
+ });
+ });
+
+ describe('Connection States', function () {
+ describe('connecting', function () {
+ it('should open the websocket connection', function () {
+ const client = new RFB(document.createElement('div'),
+ 'ws://HOST:8675/PATH');
+ sinon.spy(client._sock, 'open');
+ this.clock.tick();
+ expect(client._sock.open).to.have.been.calledOnce;
+ });
+ });
+
+ describe('connected', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ });
+
+ it('should result in a connect event if state becomes connected', function () {
+ const spy = sinon.spy();
+ client.addEventListener("connect", spy);
+ client._rfb_connection_state = 'connecting';
+ client._updateConnectionState('connected');
+ expect(spy).to.have.been.calledOnce;
+ });
+
+ it('should not result in a connect event if the state is not "connected"', function () {
+ const spy = sinon.spy();
+ client.addEventListener("connect", spy);
+ client._sock._websocket.open = () => {}; // explicitly don't call onopen
+ client._updateConnectionState('connecting');
+ expect(spy).to.not.have.been.called;
+ });
+ });
+
+ describe('disconnecting', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ });
+
+ it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
+ sinon.spy(client, '_updateConnectionState');
+ client._sock._websocket.close = () => {}; // explicitly don't call onclose
+ client._updateConnectionState('disconnecting');
+ this.clock.tick(3 * 1000);
+ expect(client._updateConnectionState).to.have.been.calledTwice;
+ expect(client._rfb_disconnect_reason).to.not.equal("");
+ expect(client._rfb_connection_state).to.equal("disconnected");
+ });
+
+ it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
+ client._updateConnectionState('disconnecting');
+ this.clock.tick(3 * 1000 / 2);
+ client._sock._websocket.close();
+ this.clock.tick(3 * 1000 / 2 + 1);
+ expect(client._rfb_connection_state).to.equal('disconnected');
+ });
+
+ it('should close the WebSocket connection', function () {
+ sinon.spy(client._sock, 'close');
+ client._updateConnectionState('disconnecting');
+ expect(client._sock.close).to.have.been.calledOnce;
+ });
+
+ it('should not result in a disconnect event', function () {
+ const spy = sinon.spy();
+ client.addEventListener("disconnect", spy);
+ client._sock._websocket.close = () => {}; // explicitly don't call onclose
+ client._updateConnectionState('disconnecting');
+ expect(spy).to.not.have.been.called;
+ });
+ });
+
+ describe('disconnected', function () {
+ let client;
+ beforeEach(function () {
+ client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
+ });
+
+ it('should result in a disconnect event if state becomes "disconnected"', function () {
+ const spy = sinon.spy();
+ client.addEventListener("disconnect", spy);
+ client._rfb_connection_state = 'disconnecting';
+ client._updateConnectionState('disconnected');
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.clean).to.be.true;
+ });
+
+ it('should result in a disconnect event without msg when no reason given', function () {
+ const spy = sinon.spy();
+ client.addEventListener("disconnect", spy);
+ client._rfb_connection_state = 'disconnecting';
+ client._rfb_disconnect_reason = "";
+ client._updateConnectionState('disconnected');
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0].length).to.equal(1);
+ });
+ });
+ });
+
+ describe('Protocol Initialization States', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ client._rfb_connection_state = 'connecting';
+ });
+
+ describe('ProtocolVersion', function () {
+ function send_ver(ver, client) {
+ const arr = new Uint8Array(12);
+ for (let i = 0; i < ver.length; i++) {
+ arr[i+4] = ver.charCodeAt(i);
+ }
+ arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
+ arr[11] = '\n';
+ client._sock._websocket._receive_data(arr);
+ }
+
+ describe('version parsing', function () {
+ it('should interpret version 003.003 as version 3.3', function () {
+ send_ver('003.003', client);
+ expect(client._rfb_version).to.equal(3.3);
+ });
+
+ it('should interpret version 003.006 as version 3.3', function () {
+ send_ver('003.006', client);
+ expect(client._rfb_version).to.equal(3.3);
+ });
+
+ it('should interpret version 003.889 as version 3.3', function () {
+ send_ver('003.889', client);
+ expect(client._rfb_version).to.equal(3.3);
+ });
+
+ it('should interpret version 003.007 as version 3.7', function () {
+ send_ver('003.007', client);
+ expect(client._rfb_version).to.equal(3.7);
+ });
+
+ it('should interpret version 003.008 as version 3.8', function () {
+ send_ver('003.008', client);
+ expect(client._rfb_version).to.equal(3.8);
+ });
+
+ it('should interpret version 004.000 as version 3.8', function () {
+ send_ver('004.000', client);
+ expect(client._rfb_version).to.equal(3.8);
+ });
+
+ it('should interpret version 004.001 as version 3.8', function () {
+ send_ver('004.001', client);
+ expect(client._rfb_version).to.equal(3.8);
+ });
+
+ it('should interpret version 005.000 as version 3.8', function () {
+ send_ver('005.000', client);
+ expect(client._rfb_version).to.equal(3.8);
+ });
+
+ it('should fail on an invalid version', function () {
+ sinon.spy(client, "_fail");
+ send_ver('002.000', client);
+ expect(client._fail).to.have.been.calledOnce;
+ });
+ });
+
+ it('should send back the interpreted version', function () {
+ send_ver('004.000', client);
+
+ const expected_str = 'RFB 003.008\n';
+ const expected = [];
+ for (let i = 0; i < expected_str.length; i++) {
+ expected[i] = expected_str.charCodeAt(i);
+ }
+
+ expect(client._sock).to.have.sent(new Uint8Array(expected));
+ });
+
+ it('should transition to the Security state on successful negotiation', function () {
+ send_ver('003.008', client);
+ expect(client._rfb_init_state).to.equal('Security');
+ });
+
+ describe('Repeater', function () {
+ beforeEach(function () {
+ client = make_rfb('wss://host:8675', { repeaterID: "12345" });
+ client._rfb_connection_state = 'connecting';
+ });
+
+ it('should interpret version 000.000 as a repeater', function () {
+ send_ver('000.000', client);
+ expect(client._rfb_version).to.equal(0);
+
+ const sent_data = client._sock._websocket._get_sent_data();
+ expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
+ expect(sent_data).to.have.length(250);
+ });
+
+ it('should handle two step repeater negotiation', function () {
+ send_ver('000.000', client);
+ send_ver('003.008', client);
+ expect(client._rfb_version).to.equal(3.8);
+ });
+ });
+ });
+
+ describe('Security', function () {
+ beforeEach(function () {
+ client._rfb_init_state = 'Security';
+ });
+
+ it('should simply receive the auth scheme when for versions < 3.7', function () {
+ client._rfb_version = 3.6;
+ const auth_scheme_raw = [1, 2, 3, 4];
+ const auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) +
+ (auth_scheme_raw[2] << 8) + auth_scheme_raw[3];
+ client._sock._websocket._receive_data(new Uint8Array(auth_scheme_raw));
+ expect(client._rfb_auth_scheme).to.equal(auth_scheme);
+ });
+
+ it('should prefer no authentication is possible', function () {
+ client._rfb_version = 3.7;
+ const auth_schemes = [2, 1, 3];
+ client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
+ expect(client._rfb_auth_scheme).to.equal(1);
+ expect(client._sock).to.have.sent(new Uint8Array([1, 1]));
+ });
+
+ it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
+ client._rfb_version = 3.7;
+ const auth_schemes = [2, 22, 16];
+ client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
+ expect(client._rfb_auth_scheme).to.equal(22);
+ expect(client._sock).to.have.sent(new Uint8Array([22]));
+ });
+
+ it('should fail if there are no supported schemes for versions >= 3.7', function () {
+ sinon.spy(client, "_fail");
+ client._rfb_version = 3.7;
+ const auth_schemes = [1, 32];
+ client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
+ expect(client._fail).to.have.been.calledOnce;
+ });
+
+ it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
+ client._rfb_version = 3.7;
+ const failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
+ sinon.spy(client, '_fail');
+ client._sock._websocket._receive_data(new Uint8Array(failure_data));
+
+ expect(client._fail).to.have.been.calledOnce;
+ expect(client._fail).to.have.been.calledWith(
+ 'Security negotiation failed on no security types (reason: whoops)');
+ });
+
+ it('should transition to the Authentication state and continue on successful negotiation', function () {
+ client._rfb_version = 3.7;
+ const auth_schemes = [1, 1];
+ client._negotiate_authentication = sinon.spy();
+ client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
+ expect(client._rfb_init_state).to.equal('Authentication');
+ expect(client._negotiate_authentication).to.have.been.calledOnce;
+ });
+ });
+
+ describe('Authentication', function () {
+ beforeEach(function () {
+ client._rfb_init_state = 'Security';
+ });
+
+ function send_security(type, cl) {
+ cl._sock._websocket._receive_data(new Uint8Array([1, type]));
+ }
+
+ it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
+ client._rfb_version = 3.6;
+ const err_msg = "Whoopsies";
+ const data = [0, 0, 0, 0];
+ const err_len = err_msg.length;
+ push32(data, err_len);
+ for (let i = 0; i < err_len; i++) {
+ data.push(err_msg.charCodeAt(i));
+ }
+
+ sinon.spy(client, '_fail');
+ client._sock._websocket._receive_data(new Uint8Array(data));
+ expect(client._fail).to.have.been.calledWith(
+ 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
+ });
+
+ it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
+ client._rfb_version = 3.8;
+ send_security(1, client);
+ expect(client._rfb_init_state).to.equal('SecurityResult');
+ });
+
+ it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
+ client._rfb_version = 3.7;
+ send_security(1, client);
+ expect(client._rfb_init_state).to.equal('ServerInitialisation');
+ });
+
+ it('should fail on an unknown auth scheme', function () {
+ sinon.spy(client, "_fail");
+ client._rfb_version = 3.8;
+ send_security(57, client);
+ expect(client._fail).to.have.been.calledOnce;
+ });
+
+ describe('VNC Authentication (type 2) Handler', function () {
+ beforeEach(function () {
+ client._rfb_init_state = 'Security';
+ client._rfb_version = 3.8;
+ });
+
+ it('should fire the credentialsrequired event if missing a password', function () {
+ const spy = sinon.spy();
+ client.addEventListener("credentialsrequired", spy);
+ send_security(2, client);
+
+ const challenge = [];
+ for (let i = 0; i < 16; i++) { challenge[i] = i; }
+ client._sock._websocket._receive_data(new Uint8Array(challenge));
+
+ expect(client._rfb_credentials).to.be.empty;
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.types).to.have.members(["password"]);
+ });
+
+ it('should encrypt the password with DES and then send it back', function () {
+ client._rfb_credentials = { password: 'passwd' };
+ send_security(2, client);
+ client._sock._websocket._get_sent_data(); // skip the choice of auth reply
+
+ const challenge = [];
+ for (let i = 0; i < 16; i++) { challenge[i] = i; }
+ client._sock._websocket._receive_data(new Uint8Array(challenge));
+
+ const des_pass = RFB.genDES('passwd', challenge);
+ expect(client._sock).to.have.sent(new Uint8Array(des_pass));
+ });
+
+ it('should transition to SecurityResult immediately after sending the password', function () {
+ client._rfb_credentials = { password: 'passwd' };
+ send_security(2, client);
+
+ const challenge = [];
+ for (let i = 0; i < 16; i++) { challenge[i] = i; }
+ client._sock._websocket._receive_data(new Uint8Array(challenge));
+
+ expect(client._rfb_init_state).to.equal('SecurityResult');
+ });
+ });
+
+ describe('XVP Authentication (type 22) Handler', function () {
+ beforeEach(function () {
+ client._rfb_init_state = 'Security';
+ client._rfb_version = 3.8;
+ });
+
+ it('should fall through to standard VNC authentication upon completion', function () {
+ client._rfb_credentials = { username: 'user',
+ target: 'target',
+ password: 'password' };
+ client._negotiate_std_vnc_auth = sinon.spy();
+ send_security(22, client);
+ expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
+ });
+
+ it('should fire the credentialsrequired event if all credentials are missing', function () {
+ const spy = sinon.spy();
+ client.addEventListener("credentialsrequired", spy);
+ client._rfb_credentials = {};
+ send_security(22, client);
+
+ expect(client._rfb_credentials).to.be.empty;
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
+ });
+
+ it('should fire the credentialsrequired event if some credentials are missing', function () {
+ const spy = sinon.spy();
+ client.addEventListener("credentialsrequired", spy);
+ client._rfb_credentials = { username: 'user',
+ target: 'target' };
+ send_security(22, client);
+
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
+ });
+
+ it('should send user and target separately', function () {
+ client._rfb_credentials = { username: 'user',
+ target: 'target',
+ password: 'password' };
+ client._negotiate_std_vnc_auth = sinon.spy();
+
+ send_security(22, client);
+
+ const expected = [22, 4, 6]; // auth selection, len user, len target
+ for (let i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
+
+ expect(client._sock).to.have.sent(new Uint8Array(expected));
+ });
+ });
+
+ describe('TightVNC Authentication (type 16) Handler', function () {
+ beforeEach(function () {
+ client._rfb_init_state = 'Security';
+ client._rfb_version = 3.8;
+ send_security(16, client);
+ client._sock._websocket._get_sent_data(); // skip the security reply
+ });
+
+ function send_num_str_pairs(pairs, client) {
+ const data = [];
+ push32(data, pairs.length);
+
+ for (let i = 0; i < pairs.length; i++) {
+ push32(data, pairs[i][0]);
+ for (let j = 0; j < 4; j++) {
+ data.push(pairs[i][1].charCodeAt(j));
+ }
+ for (let j = 0; j < 8; j++) {
+ data.push(pairs[i][2].charCodeAt(j));
+ }
+ }
+
+ client._sock._websocket._receive_data(new Uint8Array(data));
+ }
+
+ it('should skip tunnel negotiation if no tunnels are requested', function () {
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
+ expect(client._rfb_tightvnc).to.be.true;
+ });
+
+ it('should fail if no supported tunnels are listed', function () {
+ sinon.spy(client, "_fail");
+ send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
+ expect(client._fail).to.have.been.calledOnce;
+ });
+
+ it('should choose the notunnel tunnel type', function () {
+ send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
+ expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
+ });
+
+ it('should choose the notunnel tunnel type for Siemens devices', function () {
+ send_num_str_pairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client);
+ expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
+ });
+
+ it('should continue to sub-auth negotiation after tunnel negotiation', function () {
+ send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
+ client._sock._websocket._get_sent_data(); // skip the tunnel choice here
+ send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
+ expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
+ expect(client._rfb_init_state).to.equal('SecurityResult');
+ });
+
+ /*it('should attempt to use VNC auth over no auth when possible', function () {
+ client._rfb_tightvnc = true;
+ client._negotiate_std_vnc_auth = sinon.spy();
+ send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
+ expect(client._sock).to.have.sent([0, 0, 0, 1]);
+ expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
+ expect(client._rfb_auth_scheme).to.equal(2);
+ });*/ // while this would make sense, the original code doesn't actually do this
+
+ it('should accept the "no auth" auth type and transition to SecurityResult', function () {
+ client._rfb_tightvnc = true;
+ send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
+ expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
+ expect(client._rfb_init_state).to.equal('SecurityResult');
+ });
+
+ it('should accept VNC authentication and transition to that', function () {
+ client._rfb_tightvnc = true;
+ client._negotiate_std_vnc_auth = sinon.spy();
+ send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
+ expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
+ expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
+ expect(client._rfb_auth_scheme).to.equal(2);
+ });
+
+ it('should fail if there are no supported auth types', function () {
+ sinon.spy(client, "_fail");
+ client._rfb_tightvnc = true;
+ send_num_str_pairs([[23, 'stdv', 'badval__']], client);
+ expect(client._fail).to.have.been.calledOnce;
+ });
+ });
+ });
+
+ describe('SecurityResult', function () {
+ beforeEach(function () {
+ client._rfb_init_state = 'SecurityResult';
+ });
+
+ it('should fall through to ServerInitialisation on a response code of 0', function () {
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
+ expect(client._rfb_init_state).to.equal('ServerInitialisation');
+ });
+
+ it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
+ client._rfb_version = 3.8;
+ sinon.spy(client, '_fail');
+ const failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
+ client._sock._websocket._receive_data(new Uint8Array(failure_data));
+ expect(client._fail).to.have.been.calledWith(
+ 'Security negotiation failed on security result (reason: whoops)');
+ });
+
+ it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
+ sinon.spy(client, '_fail');
+ client._rfb_version = 3.7;
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
+ expect(client._fail).to.have.been.calledWith(
+ 'Security handshake failed');
+ });
+
+ it('should result in securityfailure event when receiving a non zero status', function () {
+ const spy = sinon.spy();
+ client.addEventListener("securityfailure", spy);
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.status).to.equal(2);
+ });
+
+ it('should include reason when provided in securityfailure event', function () {
+ client._rfb_version = 3.8;
+ const spy = sinon.spy();
+ client.addEventListener("securityfailure", spy);
+ const failure_data = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
+ 32, 102, 97, 105, 108, 117, 114, 101];
+ client._sock._websocket._receive_data(new Uint8Array(failure_data));
+ expect(spy.args[0][0].detail.status).to.equal(1);
+ expect(spy.args[0][0].detail.reason).to.equal('such failure');
+ });
+
+ it('should not include reason when length is zero in securityfailure event', function () {
+ client._rfb_version = 3.9;
+ const spy = sinon.spy();
+ client.addEventListener("securityfailure", spy);
+ const failure_data = [0, 0, 0, 1, 0, 0, 0, 0];
+ client._sock._websocket._receive_data(new Uint8Array(failure_data));
+ expect(spy.args[0][0].detail.status).to.equal(1);
+ expect('reason' in spy.args[0][0].detail).to.be.false;
+ });
+
+ it('should not include reason in securityfailure event for version < 3.8', function () {
+ client._rfb_version = 3.6;
+ const spy = sinon.spy();
+ client.addEventListener("securityfailure", spy);
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
+ expect(spy.args[0][0].detail.status).to.equal(2);
+ expect('reason' in spy.args[0][0].detail).to.be.false;
+ });
+ });
+
+ describe('ClientInitialisation', function () {
+ it('should transition to the ServerInitialisation state', function () {
+ const client = make_rfb();
+ client._rfb_connection_state = 'connecting';
+ client._rfb_init_state = 'SecurityResult';
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
+ expect(client._rfb_init_state).to.equal('ServerInitialisation');
+ });
+
+ it('should send 1 if we are in shared mode', function () {
+ const client = make_rfb('wss://host:8675', { shared: true });
+ client._rfb_connection_state = 'connecting';
+ client._rfb_init_state = 'SecurityResult';
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
+ expect(client._sock).to.have.sent(new Uint8Array([1]));
+ });
+
+ it('should send 0 if we are not in shared mode', function () {
+ const client = make_rfb('wss://host:8675', { shared: false });
+ client._rfb_connection_state = 'connecting';
+ client._rfb_init_state = 'SecurityResult';
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
+ expect(client._sock).to.have.sent(new Uint8Array([0]));
+ });
+ });
+
+ describe('ServerInitialisation', function () {
+ beforeEach(function () {
+ client._rfb_init_state = 'ServerInitialisation';
+ });
+
+ function send_server_init(opts, client) {
+ const full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0,
+ true_color: 1, red_max: 255, green_max: 255, blue_max: 255,
+ red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' };
+ for (let opt in opts) {
+ full_opts[opt] = opts[opt];
+ }
+ const data = [];
+
+ push16(data, full_opts.width);
+ push16(data, full_opts.height);
+
+ data.push(full_opts.bpp);
+ data.push(full_opts.depth);
+ data.push(full_opts.big_endian);
+ data.push(full_opts.true_color);
+
+ push16(data, full_opts.red_max);
+ push16(data, full_opts.green_max);
+ push16(data, full_opts.blue_max);
+ push8(data, full_opts.red_shift);
+ push8(data, full_opts.green_shift);
+ push8(data, full_opts.blue_shift);
+
+ // padding
+ push8(data, 0);
+ push8(data, 0);
+ push8(data, 0);
+
+ client._sock._websocket._receive_data(new Uint8Array(data));
+
+ const name_data = [];
+ push32(name_data, full_opts.name.length);
+ for (let i = 0; i < full_opts.name.length; i++) {
+ name_data.push(full_opts.name.charCodeAt(i));
+ }
+ client._sock._websocket._receive_data(new Uint8Array(name_data));
+ }
+
+ it('should set the framebuffer width and height', function () {
+ send_server_init({ width: 32, height: 84 }, client);
+ expect(client._fb_width).to.equal(32);
+ expect(client._fb_height).to.equal(84);
+ });
+
+ // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
+
+ it('should set the framebuffer name and call the callback', function () {
+ const spy = sinon.spy();
+ client.addEventListener("desktopname", spy);
+ send_server_init({ name: 'some name' }, client);
+
+ expect(client._fb_name).to.equal('some name');
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.name).to.equal('some name');
+ });
+
+ it('should handle the extended init message of the tight encoding', function () {
+ // NB(sross): we don't actually do anything with it, so just test that we can
+ // read it w/o throwing an error
+ client._rfb_tightvnc = true;
+ send_server_init({}, client);
+
+ const tight_data = [];
+ push16(tight_data, 1);
+ push16(tight_data, 2);
+ push16(tight_data, 3);
+ push16(tight_data, 0);
+ for (let i = 0; i < 16 + 32 + 48; i++) {
+ tight_data.push(i);
+ }
+ client._sock._websocket._receive_data(new Uint8Array(tight_data));
+
+ expect(client._rfb_connection_state).to.equal('connected');
+ });
+
+ it('should resize the display', function () {
+ sinon.spy(client._display, 'resize');
+ send_server_init({ width: 27, height: 32 }, client);
+
+ expect(client._display.resize).to.have.been.calledOnce;
+ expect(client._display.resize).to.have.been.calledWith(27, 32);
+ });
+
+ it('should grab the mouse and keyboard', function () {
+ sinon.spy(client._keyboard, 'grab');
+ sinon.spy(client._mouse, 'grab');
+ send_server_init({}, client);
+ expect(client._keyboard.grab).to.have.been.calledOnce;
+ expect(client._mouse.grab).to.have.been.calledOnce;
+ });
+
+ describe('Initial Update Request', function () {
+ beforeEach(function () {
+ sinon.spy(RFB.messages, "pixelFormat");
+ sinon.spy(RFB.messages, "clientEncodings");
+ sinon.spy(RFB.messages, "fbUpdateRequest");
+ });
+
+ afterEach(function () {
+ RFB.messages.pixelFormat.restore();
+ RFB.messages.clientEncodings.restore();
+ RFB.messages.fbUpdateRequest.restore();
+ });
+
+ // TODO(directxman12): test the various options in this configuration matrix
+ it('should reply with the pixel format, client encodings, and initial update request', function () {
+ send_server_init({ width: 27, height: 32 }, client);
+
+ expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
+ expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 24, true);
+ expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
+ expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
+ expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.encodingTight);
+ expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
+ expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
+ expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
+ });
+
+ it('should reply with restricted settings for Intel AMT servers', function () {
+ send_server_init({ width: 27, height: 32, name: "Intel(r) AMT KVM"}, client);
+
+ expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
+ expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 8, true);
+ expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
+ expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
+ expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingTight);
+ expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingHextile);
+ expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
+ expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
+ expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
+ });
+ });
+
+ it('should transition to the "connected" state', function () {
+ send_server_init({}, client);
+ expect(client._rfb_connection_state).to.equal('connected');
+ });
+ });
+ });
+
+ describe('Protocol Message Processing After Completing Initialization', function () {
+ let client;
+
+ beforeEach(function () {
+ client = make_rfb();
+ client._fb_name = 'some device';
+ client._fb_width = 640;
+ client._fb_height = 20;
+ });
+
+ describe('Framebuffer Update Handling', function () {
+ const target_data_arr = [
+ 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
+ 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
+ 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
+ 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
+ ];
+ let target_data;
+
+ const target_data_check_arr = [
+ 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
+ 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
+ 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
+ 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
+ ];
+ let target_data_check;
+
+ before(function () {
+ // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
+ target_data = new Uint8Array(target_data_arr);
+ target_data_check = new Uint8Array(target_data_check_arr);
+ });
+
+ function send_fbu_msg(rect_info, rect_data, client, rect_cnt) {
+ let data = [];
+
+ if (!rect_cnt || rect_cnt > -1) {
+ // header
+ data.push(0); // msg type
+ data.push(0); // padding
+ push16(data, rect_cnt || rect_data.length);
+ }
+
+ for (let i = 0; i < rect_data.length; i++) {
+ if (rect_info[i]) {
+ push16(data, rect_info[i].x);
+ push16(data, rect_info[i].y);
+ push16(data, rect_info[i].width);
+ push16(data, rect_info[i].height);
+ push32(data, rect_info[i].encoding);
+ }
+ data = data.concat(rect_data[i]);
+ }
+
+ client._sock._websocket._receive_data(new Uint8Array(data));
+ }
+
+ it('should send an update request if there is sufficient data', function () {
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
+ RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
+
+ client._framebufferUpdate = () => true;
+ client._sock._websocket._receive_data(new Uint8Array([0]));
+
+ expect(client._sock).to.have.sent(expected_msg._sQ);
+ });
+
+ it('should not send an update request if we need more data', function () {
+ client._sock._websocket._receive_data(new Uint8Array([0]));
+ expect(client._sock._websocket._get_sent_data()).to.have.length(0);
+ });
+
+ it('should resume receiving an update if we previously did not have enough data', function () {
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
+ RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
+
+ // just enough to set FBU.rects
+ client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
+ expect(client._sock._websocket._get_sent_data()).to.have.length(0);
+
+ client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; }; // we magically have enough data
+ // 247 should *not* be used as the message type here
+ client._sock._websocket._receive_data(new Uint8Array([247]));
+ expect(client._sock).to.have.sent(expected_msg._sQ);
+ });
+
+ it('should not send a request in continuous updates mode', function () {
+ client._enabledContinuousUpdates = true;
+ client._framebufferUpdate = () => true;
+ client._sock._websocket._receive_data(new Uint8Array([0]));
+
+ expect(client._sock._websocket._get_sent_data()).to.have.length(0);
+ });
+
+ it('should fail on an unsupported encoding', function () {
+ sinon.spy(client, "_fail");
+ const rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
+ send_fbu_msg([rect_info], [[]], client);
+ expect(client._fail).to.have.been.calledOnce;
+ });
+
+ it('should be able to pause and resume receiving rects if not enought data', function () {
+ // seed some initial data to copy
+ client._fb_width = 4;
+ client._fb_height = 4;
+ client._display.resize(4, 4);
+ client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
+
+ const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
+ { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
+ // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
+ const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
+ send_fbu_msg([info[0]], [rects[0]], client, 2);
+ send_fbu_msg([info[1]], [rects[1]], client, -1);
+ expect(client._display).to.have.displayed(target_data_check);
+ });
+
+ describe('Message Encoding Handlers', function () {
+ beforeEach(function () {
+ // a really small frame
+ client._fb_width = 4;
+ client._fb_height = 4;
+ client._fb_depth = 24;
+ client._display.resize(4, 4);
+ });
+
+ it('should handle the RAW encoding', function () {
+ const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
+ { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
+ { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
+ { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
+ // data is in bgrx
+ const rects = [
+ [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
+ [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
+ [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
+ [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
+ send_fbu_msg(info, rects, client);
+ expect(client._display).to.have.displayed(target_data);
+ });
+
+ it('should handle the RAW encoding in low colour mode', function () {
+ const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
+ { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
+ { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
+ { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
+ const rects = [
+ [0x03, 0x03, 0x03, 0x03],
+ [0x0c, 0x0c, 0x0c, 0x0c],
+ [0x0c, 0x0c, 0x03, 0x03],
+ [0x0c, 0x0c, 0x03, 0x03]];
+ client._fb_depth = 8;
+ send_fbu_msg(info, rects, client);
+ expect(client._display).to.have.displayed(target_data_check);
+ });
+
+ it('should handle the COPYRECT encoding', function () {
+ // seed some initial data to copy
+ client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
+
+ const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
+ { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
+ // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
+ const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
+ send_fbu_msg(info, rects, client);
+ expect(client._display).to.have.displayed(target_data_check);
+ });
+
+ // TODO(directxman12): for encodings with subrects, test resuming on partial send?
+ // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
+
+ it('should handle the RRE encoding', function () {
+ const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
+ const rect = [];
+ push32(rect, 2); // 2 subrects
+ push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
+ rect.push(0xff); // becomes ff0000ff --> #0000FF color
+ rect.push(0x00);
+ rect.push(0x00);
+ rect.push(0xff);
+ push16(rect, 0); // x: 0
+ push16(rect, 0); // y: 0
+ push16(rect, 2); // width: 2
+ push16(rect, 2); // height: 2
+ rect.push(0xff); // becomes ff0000ff --> #0000FF color
+ rect.push(0x00);
+ rect.push(0x00);
+ rect.push(0xff);
+ push16(rect, 2); // x: 2
+ push16(rect, 2); // y: 2
+ push16(rect, 2); // width: 2
+ push16(rect, 2); // height: 2
+
+ send_fbu_msg(info, [rect], client);
+ expect(client._display).to.have.displayed(target_data_check);
+ });
+
+ describe('the HEXTILE encoding handler', function () {
+ it('should handle a tile with fg, bg specified, normal subrects', function () {
+ const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
+ const rect = [];
+ rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
+ push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
+ rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
+ rect.push(0x00);
+ rect.push(0x00);
+ rect.push(0xff);
+ rect.push(2); // 2 subrects
+ rect.push(0); // x: 0, y: 0
+ rect.push(1 | (1 << 4)); // width: 2, height: 2
+ rect.push(2 | (2 << 4)); // x: 2, y: 2
+ rect.push(1 | (1 << 4)); // width: 2, height: 2
+ send_fbu_msg(info, [rect], client);
+ expect(client._display).to.have.displayed(target_data_check);
+ });
+
+ it('should handle a raw tile', function () {
+ const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
+ const rect = [];
+ rect.push(0x01); // raw
+ for (let i = 0; i < target_data.length; i += 4) {
+ rect.push(target_data[i + 2]);
+ rect.push(target_data[i + 1]);
+ rect.push(target_data[i]);
+ rect.push(target_data[i + 3]);
+ }
+ send_fbu_msg(info, [rect], client);
+ expect(client._display).to.have.displayed(target_data);
+ });
+
+ it('should handle a tile with only bg specified (solid bg)', function () {
+ const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
+ const rect = [];
+ rect.push(0x02);
+ push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
+ send_fbu_msg(info, [rect], client);
+
+ const expected = [];
+ for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
+ expect(client._display).to.have.displayed(new Uint8Array(expected));
+ });
+
+ it('should handle a tile with only bg specified and an empty frame afterwards', function () {
+ // set the width so we can have two tiles
+ client._fb_width = 8;
+ client._display.resize(8, 4);
+
+ const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
+
+ const rect = [];
+
+ // send a bg frame
+ rect.push(0x02);
+ push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
+
+ // send an empty frame
+ rect.push(0x00);
+
+ send_fbu_msg(info, [rect], client);
+
+ const expected = [];
+ for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid
+ for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color
+ expect(client._display).to.have.displayed(new Uint8Array(expected));
+ });
+
+ it('should handle a tile with bg and coloured subrects', function () {
+ const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
+ const rect = [];
+ rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
+ push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
+ rect.push(2); // 2 subrects
+ rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
+ rect.push(0x00);
+ rect.push(0x00);
+ rect.push(0xff);
+ rect.push(0); // x: 0, y: 0
+ rect.push(1 | (1 << 4)); // width: 2, height: 2
+ rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
+ rect.push(0x00);
+ rect.push(0x00);
+ rect.push(0xff);
+ rect.push(2 | (2 << 4)); // x: 2, y: 2
+ rect.push(1 | (1 << 4)); // width: 2, height: 2
+ send_fbu_msg(info, [rect], client);
+ expect(client._display).to.have.displayed(target_data_check);
+ });
+
+ it('should carry over fg and bg colors from the previous tile if not specified', function () {
+ client._fb_width = 4;
+ client._fb_height = 17;
+ client._display.resize(4, 17);
+
+ const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
+ const rect = [];
+ rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
+ push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
+ rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
+ rect.push(0x00);
+ rect.push(0x00);
+ rect.push(0xff);
+ rect.push(8); // 8 subrects
+ for (let i = 0; i < 4; i++) {
+ rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
+ rect.push(1 | (1 << 4)); // width: 2, height: 2
+ rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
+ rect.push(1 | (1 << 4)); // width: 2, height: 2
+ }
+ rect.push(0x08); // anysubrects
+ rect.push(1); // 1 subrect
+ rect.push(0); // x: 0, y: 0
+ rect.push(1 | (1 << 4)); // width: 2, height: 2
+ send_fbu_msg(info, [rect], client);
+
+ let expected = [];
+ for (let i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
+ expected = expected.concat(target_data_check_arr.slice(0, 16));
+ expect(client._display).to.have.displayed(new Uint8Array(expected));
+ });
+
+ it('should fail on an invalid subencoding', function () {
+ sinon.spy(client, "_fail");
+ const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
+ const rects = [[45]]; // an invalid subencoding
+ send_fbu_msg(info, rects, client);
+ expect(client._fail).to.have.been.calledOnce;
+ });
+ });
+
+ it.skip('should handle the TIGHT encoding', function () {
+ // TODO(directxman12): test this
+ });
+
+ it.skip('should handle the TIGHT_PNG encoding', function () {
+ // TODO(directxman12): test this
+ });
+
+ it('should handle the DesktopSize pseduo-encoding', function () {
+ sinon.spy(client._display, 'resize');
+ send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
+
+ expect(client._fb_width).to.equal(20);
+ expect(client._fb_height).to.equal(50);
+
+ expect(client._display.resize).to.have.been.calledOnce;
+ expect(client._display.resize).to.have.been.calledWith(20, 50);
+ });
+
+ describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
+ beforeEach(function () {
+ // a really small frame
+ client._fb_width = 4;
+ client._fb_height = 4;
+ client._display.resize(4, 4);
+ sinon.spy(client._display, 'resize');
+ });
+
+ function make_screen_data(nr_of_screens) {
+ const data = [];
+ push8(data, nr_of_screens); // number-of-screens
+ push8(data, 0); // padding
+ push16(data, 0); // padding
+ for (let i=0; i<nr_of_screens; i += 1) {
+ push32(data, 0); // id
+ push16(data, 0); // x-position
+ push16(data, 0); // y-position
+ push16(data, 20); // width
+ push16(data, 50); // height
+ push32(data, 0); // flags
+ }
+ return data;
+ }
+
+ it('should handle a resize requested by this client', function () {
+ const reason_for_change = 1; // requested by this client
+ const status_code = 0; // No error
+
+ send_fbu_msg([{ x: reason_for_change, y: status_code,
+ width: 20, height: 50, encoding: -308 }],
+ make_screen_data(1), client);
+
+ expect(client._fb_width).to.equal(20);
+ expect(client._fb_height).to.equal(50);
+
+ expect(client._display.resize).to.have.been.calledOnce;
+ expect(client._display.resize).to.have.been.calledWith(20, 50);
+ });
+
+ it('should handle a resize requested by another client', function () {
+ const reason_for_change = 2; // requested by another client
+ const status_code = 0; // No error
+
+ send_fbu_msg([{ x: reason_for_change, y: status_code,
+ width: 20, height: 50, encoding: -308 }],
+ make_screen_data(1), client);
+
+ expect(client._fb_width).to.equal(20);
+ expect(client._fb_height).to.equal(50);
+
+ expect(client._display.resize).to.have.been.calledOnce;
+ expect(client._display.resize).to.have.been.calledWith(20, 50);
+ });
+
+ it('should be able to recieve requests which contain data for multiple screens', function () {
+ const reason_for_change = 2; // requested by another client
+ const status_code = 0; // No error
+
+ send_fbu_msg([{ x: reason_for_change, y: status_code,
+ width: 60, height: 50, encoding: -308 }],
+ make_screen_data(3), client);
+
+ expect(client._fb_width).to.equal(60);
+ expect(client._fb_height).to.equal(50);
+
+ expect(client._display.resize).to.have.been.calledOnce;
+ expect(client._display.resize).to.have.been.calledWith(60, 50);
+ });
+
+ it('should not handle a failed request', function () {
+ const reason_for_change = 1; // requested by this client
+ const status_code = 1; // Resize is administratively prohibited
+
+ send_fbu_msg([{ x: reason_for_change, y: status_code,
+ width: 20, height: 50, encoding: -308 }],
+ make_screen_data(1), client);
+
+ expect(client._fb_width).to.equal(4);
+ expect(client._fb_height).to.equal(4);
+
+ expect(client._display.resize).to.not.have.been.called;
+ });
+ });
+
+ describe('the Cursor pseudo-encoding handler', function () {
+ beforeEach(function () {
+ sinon.spy(client._cursor, 'change');
+ });
+
+ it('should handle a standard cursor', function () {
+ const info = { x: 5, y: 7,
+ width: 4, height: 4,
+ encoding: -239};
+ let rect = [];
+ let expected = [];
+
+ for (let i = 0;i < info.width*info.height;i++) {
+ push32(rect, 0x11223300);
+ }
+ push32(rect, 0xa0a0a0a0);
+
+ for (let i = 0;i < info.width*info.height/2;i++) {
+ push32(expected, 0x332211ff);
+ push32(expected, 0x33221100);
+ }
+ expected = new Uint8Array(expected);
+
+ send_fbu_msg([info], [rect], client);
+
+ expect(client._cursor.change).to.have.been.calledOnce;
+ expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
+ });
+
+ it('should handle an empty cursor', function () {
+ const info = { x: 0, y: 0,
+ width: 0, height: 0,
+ encoding: -239};
+ const rect = [];
+
+ send_fbu_msg([info], [rect], client);
+
+ expect(client._cursor.change).to.have.been.calledOnce;
+ expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);
+ });
+
+ it('should handle a transparent cursor', function () {
+ const info = { x: 5, y: 7,
+ width: 4, height: 4,
+ encoding: -239};
+ let rect = [];
+ let expected = [];
+
+ for (let i = 0;i < info.width*info.height;i++) {
+ push32(rect, 0x11223300);
+ }
+ push32(rect, 0x00000000);
+
+ for (let i = 0;i < info.width*info.height;i++) {
+ push32(expected, 0x33221100);
+ }
+ expected = new Uint8Array(expected);
+
+ send_fbu_msg([info], [rect], client);
+
+ expect(client._cursor.change).to.have.been.calledOnce;
+ expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
+ });
+
+ describe('dot for empty cursor', function () {
+ beforeEach(function () {
+ client.showDotCursor = true;
+ // Was called when we enabled dot cursor
+ client._cursor.change.reset();
+ });
+
+ it('should show a standard cursor', function () {
+ const info = { x: 5, y: 7,
+ width: 4, height: 4,
+ encoding: -239};
+ let rect = [];
+ let expected = [];
+
+ for (let i = 0;i < info.width*info.height;i++) {
+ push32(rect, 0x11223300);
+ }
+ push32(rect, 0xa0a0a0a0);
+
+ for (let i = 0;i < info.width*info.height/2;i++) {
+ push32(expected, 0x332211ff);
+ push32(expected, 0x33221100);
+ }
+ expected = new Uint8Array(expected);
+
+ send_fbu_msg([info], [rect], client);
+
+ expect(client._cursor.change).to.have.been.calledOnce;
+ expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
+ });
+
+ it('should handle an empty cursor', function () {
+ const info = { x: 0, y: 0,
+ width: 0, height: 0,
+ encoding: -239};
+ const rect = [];
+ const dot = RFB.cursors.dot;
+
+ send_fbu_msg([info], [rect], client);
+
+ expect(client._cursor.change).to.have.been.calledOnce;
+ expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
+ dot.hotx,
+ dot.hoty,
+ dot.w,
+ dot.h);
+ });
+
+ it('should handle a transparent cursor', function () {
+ const info = { x: 5, y: 7,
+ width: 4, height: 4,
+ encoding: -239};
+ let rect = [];
+ const dot = RFB.cursors.dot;
+
+ for (let i = 0;i < info.width*info.height;i++) {
+ push32(rect, 0x11223300);
+ }
+ push32(rect, 0x00000000);
+
+ send_fbu_msg([info], [rect], client);
+
+ expect(client._cursor.change).to.have.been.calledOnce;
+ expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
+ dot.hotx,
+ dot.hoty,
+ dot.w,
+ dot.h);
+ });
+ });
+ });
+
+ it('should handle the last_rect pseudo-encoding', function () {
+ send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
+ expect(client._FBU.rects).to.equal(0);
+ });
+ });
+ });
+
+ describe('XVP Message Handling', function () {
+ it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
+ const spy = sinon.spy();
+ client.addEventListener("capabilities", spy);
+ client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
+ expect(client._rfb_xvp_ver).to.equal(10);
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.capabilities.power).to.be.true;
+ expect(client.capabilities.power).to.be.true;
+ });
+
+ it('should fail on unknown XVP message types', function () {
+ sinon.spy(client, "_fail");
+ client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
+ expect(client._fail).to.have.been.calledOnce;
+ });
+ });
+
+ it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
+ const expected_str = 'cheese!';
+ const data = [3, 0, 0, 0];
+ push32(data, expected_str.length);
+ for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
+ const spy = sinon.spy();
+ client.addEventListener("clipboard", spy);
+
+ client._sock._websocket._receive_data(new Uint8Array(data));
+ expect(spy).to.have.been.calledOnce;
+ expect(spy.args[0][0].detail.text).to.equal(expected_str);
+ });
+
+ it('should fire the bell callback on Bell', function () {
+ const spy = sinon.spy();
+ client.addEventListener("bell", spy);
+ client._sock._websocket._receive_data(new Uint8Array([2]));
+ expect(spy).to.have.been.calledOnce;
+ });
+
+ it('should respond correctly to ServerFence', function () {
+ const expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
+ const incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
+
+ const payload = "foo\x00ab9";
+
+ // ClientFence and ServerFence are identical in structure
+ RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload);
+ RFB.messages.clientFence(incoming_msg, 0xffffffff, payload);
+
+ client._sock._websocket._receive_data(incoming_msg._sQ);
+
+ expect(client._sock).to.have.sent(expected_msg._sQ);
+
+ expected_msg._sQlen = 0;
+ incoming_msg._sQlen = 0;
+
+ RFB.messages.clientFence(expected_msg, (1<<0), payload);
+ RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload);
+
+ client._sock._websocket._receive_data(incoming_msg._sQ);
+
+ expect(client._sock).to.have.sent(expected_msg._sQ);
+ });
+
+ it('should enable continuous updates on first EndOfContinousUpdates', function () {
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
+
+ RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20);
+
+ expect(client._enabledContinuousUpdates).to.be.false;
+
+ client._sock._websocket._receive_data(new Uint8Array([150]));
+
+ expect(client._enabledContinuousUpdates).to.be.true;
+ expect(client._sock).to.have.sent(expected_msg._sQ);
+ });
+
+ it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
+ client._enabledContinuousUpdates = true;
+ client._supportsContinuousUpdates = true;
+
+ client._sock._websocket._receive_data(new Uint8Array([150]));
+
+ expect(client._enabledContinuousUpdates).to.be.false;
+ });
+
+ it('should update continuous updates on resize', function () {
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
+ RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700);
+
+ client._resize(450, 160);
+
+ expect(client._sock._websocket._get_sent_data()).to.have.length(0);
+
+ client._enabledContinuousUpdates = true;
+
+ client._resize(90, 700);
+
+ expect(client._sock).to.have.sent(expected_msg._sQ);
+ });
+
+ it('should fail on an unknown message type', function () {
+ sinon.spy(client, "_fail");
+ client._sock._websocket._receive_data(new Uint8Array([87]));
+ expect(client._fail).to.have.been.calledOnce;
+ });
+ });
+
+ describe('Asynchronous Events', function () {
+ let client;
+ beforeEach(function () {
+ client = make_rfb();
+ });
+
+ describe('Mouse event handlers', function () {
+ it('should not send button messages in view-only mode', function () {
+ client._viewOnly = true;
+ sinon.spy(client._sock, 'flush');
+ client._handleMouseButton(0, 0, 1, 0x001);
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+
+ it('should not send movement messages in view-only mode', function () {
+ client._viewOnly = true;
+ sinon.spy(client._sock, 'flush');
+ client._handleMouseMove(0, 0);
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+
+ it('should send a pointer event on mouse button presses', function () {
+ client._handleMouseButton(10, 12, 1, 0x001);
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
+ RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
+ expect(client._sock).to.have.sent(pointer_msg._sQ);
+ });
+
+ it('should send a mask of 1 on mousedown', function () {
+ client._handleMouseButton(10, 12, 1, 0x001);
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
+ RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
+ expect(client._sock).to.have.sent(pointer_msg._sQ);
+ });
+
+ it('should send a mask of 0 on mouseup', function () {
+ client._mouse_buttonMask = 0x001;
+ client._handleMouseButton(10, 12, 0, 0x001);
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
+ RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
+ expect(client._sock).to.have.sent(pointer_msg._sQ);
+ });
+
+ it('should send a pointer event on mouse movement', function () {
+ client._handleMouseMove(10, 12);
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
+ RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
+ expect(client._sock).to.have.sent(pointer_msg._sQ);
+ });
+
+ it('should set the button mask so that future mouse movements use it', function () {
+ client._handleMouseButton(10, 12, 1, 0x010);
+ client._handleMouseMove(13, 9);
+ const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
+ RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
+ RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
+ expect(client._sock).to.have.sent(pointer_msg._sQ);
+ });
+ });
+
+ describe('Keyboard Event Handlers', function () {
+ it('should send a key message on a key press', function () {
+ client._handleKeyEvent(0x41, 'KeyA', true);
+ const key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
+ RFB.messages.keyEvent(key_msg, 0x41, 1);
+ expect(client._sock).to.have.sent(key_msg._sQ);
+ });
+
+ it('should not send messages in view-only mode', function () {
+ client._viewOnly = true;
+ sinon.spy(client._sock, 'flush');
+ client._handleKeyEvent('a', 'KeyA', true);
+ expect(client._sock.flush).to.not.have.been.called;
+ });
+ });
+
+ describe('WebSocket event handlers', function () {
+ // message events
+ it('should do nothing if we receive an empty message and have nothing in the queue', function () {
+ client._normal_msg = sinon.spy();
+ client._sock._websocket._receive_data(new Uint8Array([]));
+ expect(client._normal_msg).to.not.have.been.called;
+ });
+
+ it('should handle a message in the connected state as a normal message', function () {
+ client._normal_msg = sinon.spy();
+ client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
+ expect(client._normal_msg).to.have.been.called;
+ });
+
+ it('should handle a message in any non-disconnected/failed state like an init message', function () {
+ client._rfb_connection_state = 'connecting';
+ client._rfb_init_state = 'ProtocolVersion';
+ client._init_msg = sinon.spy();
+ client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
+ expect(client._init_msg).to.have.been.called;
+ });
+
+ it('should process all normal messages directly', function () {
+ const spy = sinon.spy();
+ client.addEventListener("bell", spy);
+ client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
+ expect(spy).to.have.been.calledTwice;
+ });
+
+ // open events
+ it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
+ client = new RFB(document.createElement('div'), 'wss://host:8675');
+ this.clock.tick();
+ client._sock._websocket._open();
+ expect(client._rfb_init_state).to.equal('ProtocolVersion');
+ });
+
+ it('should fail if we are not currently ready to connect and we get an "open" event', function () {
+ sinon.spy(client, "_fail");
+ client._rfb_connection_state = 'connected';
+ client._sock._websocket._open();
+ expect(client._fail).to.have.been.calledOnce;
+ });
+
+ // close events
+ it('should transition to "disconnected" from "disconnecting" on a close event', function () {
+ const real = client._sock._websocket.close;
+ client._sock._websocket.close = () => {};
+ client.disconnect();
+ expect(client._rfb_connection_state).to.equal('disconnecting');
+ client._sock._websocket.close = real;
+ client._sock._websocket.close();
+ expect(client._rfb_connection_state).to.equal('disconnected');
+ });
+
+ it('should fail if we get a close event while connecting', function () {
+ sinon.spy(client, "_fail");
+ client._rfb_connection_state = 'connecting';
+ client._sock._websocket.close();
+ expect(client._fail).to.have.been.calledOnce;
+ });
+
+ it('should unregister close event handler', function () {
+ sinon.spy(client._sock, 'off');
+ client.disconnect();
+ client._sock._websocket.close();
+ expect(client._sock.off).to.have.been.calledWith('close');
+ });
+
+ // error events do nothing
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/test.util.js b/systemvm/agent/noVNC/tests/test.util.js
new file mode 100644
index 0000000..201acc8
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.util.js
@@ -0,0 +1,69 @@
+/* eslint-disable no-console */
+const expect = chai.expect;
+
+import * as Log from '../core/util/logging.js';
+
+describe('Utils', function () {
+ "use strict";
+
+ describe('logging functions', function () {
+ beforeEach(function () {
+ sinon.spy(console, 'log');
+ sinon.spy(console, 'debug');
+ sinon.spy(console, 'warn');
+ sinon.spy(console, 'error');
+ sinon.spy(console, 'info');
+ });
+
+ afterEach(function () {
+ console.log.restore();
+ console.debug.restore();
+ console.warn.restore();
+ console.error.restore();
+ console.info.restore();
+ Log.init_logging();
+ });
+
+ it('should use noop for levels lower than the min level', function () {
+ Log.init_logging('warn');
+ Log.Debug('hi');
+ Log.Info('hello');
+ expect(console.log).to.not.have.been.called;
+ });
+
+ it('should use console.debug for Debug', function () {
+ Log.init_logging('debug');
+ Log.Debug('dbg');
+ expect(console.debug).to.have.been.calledWith('dbg');
+ });
+
+ it('should use console.info for Info', function () {
+ Log.init_logging('debug');
+ Log.Info('inf');
+ expect(console.info).to.have.been.calledWith('inf');
+ });
+
+ it('should use console.warn for Warn', function () {
+ Log.init_logging('warn');
+ Log.Warn('wrn');
+ expect(console.warn).to.have.been.called;
+ expect(console.warn).to.have.been.calledWith('wrn');
+ });
+
+ it('should use console.error for Error', function () {
+ Log.init_logging('error');
+ Log.Error('err');
+ expect(console.error).to.have.been.called;
+ expect(console.error).to.have.been.calledWith('err');
+ });
+ });
+
+ // TODO(directxman12): test the conf_default and conf_defaults methods
+ // TODO(directxman12): test decodeUTF8
+ // TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
+ // TODO(directxman12): figure out a good way to test getPosition and getEventPosition
+ // TODO(directxman12): figure out how to test the browser detection functions properly
+ // (we can't really test them against the browsers, except for Gecko
+ // via PhantomJS, the default test driver)
+});
+/* eslint-enable no-console */
diff --git a/systemvm/agent/noVNC/tests/test.websock.js b/systemvm/agent/noVNC/tests/test.websock.js
new file mode 100644
index 0000000..30e19e9
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.websock.js
@@ -0,0 +1,441 @@
+const expect = chai.expect;
+
+import Websock from '../core/websock.js';
+import FakeWebSocket from './fake.websocket.js';
+
+describe('Websock', function () {
+ "use strict";
+
+ describe('Queue methods', function () {
+ let sock;
+ const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
+
+ beforeEach(function () {
+ sock = new Websock();
+ // skip init
+ sock._allocate_buffers();
+ sock._rQ.set(RQ_TEMPLATE);
+ sock._rQlen = RQ_TEMPLATE.length;
+ });
+ describe('rQlen', function () {
+ it('should return the length of the receive queue', function () {
+ sock.rQi = 0;
+
+ expect(sock.rQlen).to.equal(RQ_TEMPLATE.length);
+ });
+
+ it("should return the proper length if we read some from the receive queue", function () {
+ sock.rQi = 1;
+
+ expect(sock.rQlen).to.equal(RQ_TEMPLATE.length - 1);
+ });
+ });
+
+ describe('rQpeek8', function () {
+ it('should peek at the next byte without poping it off the queue', function () {
+ const bef_len = sock.rQlen;
+ const peek = sock.rQpeek8();
+ expect(sock.rQpeek8()).to.equal(peek);
+ expect(sock.rQlen).to.equal(bef_len);
+ });
+ });
+
+ describe('rQshift8()', function () {
+ it('should pop a single byte from the receive queue', function () {
+ const peek = sock.rQpeek8();
+ const bef_len = sock.rQlen;
+ expect(sock.rQshift8()).to.equal(peek);
+ expect(sock.rQlen).to.equal(bef_len - 1);
+ });
+ });
+
+ describe('rQshift16()', function () {
+ it('should pop two bytes from the receive queue and return a single number', function () {
+ const bef_len = sock.rQlen;
+ const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
+ expect(sock.rQshift16()).to.equal(expected);
+ expect(sock.rQlen).to.equal(bef_len - 2);
+ });
+ });
+
+ describe('rQshift32()', function () {
+ it('should pop four bytes from the receive queue and return a single number', function () {
+ const bef_len = sock.rQlen;
+ const expected = (RQ_TEMPLATE[0] << 24) +
+ (RQ_TEMPLATE[1] << 16) +
+ (RQ_TEMPLATE[2] << 8) +
+ RQ_TEMPLATE[3];
+ expect(sock.rQshift32()).to.equal(expected);
+ expect(sock.rQlen).to.equal(bef_len - 4);
+ });
+ });
+
+ describe('rQshiftStr', function () {
+ it('should shift the given number of bytes off of the receive queue and return a string', function () {
+ const bef_len = sock.rQlen;
+ const bef_rQi = sock.rQi;
+ const shifted = sock.rQshiftStr(3);
+ expect(shifted).to.be.a('string');
+ expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3))));
+ expect(sock.rQlen).to.equal(bef_len - 3);
+ });
+
+ it('should shift the entire rest of the queue off if no length is given', function () {
+ sock.rQshiftStr();
+ expect(sock.rQlen).to.equal(0);
+ });
+
+ it('should be able to handle very large strings', function () {
+ const BIG_LEN = 500000;
+ const RQ_BIG = new Uint8Array(BIG_LEN);
+ let expected = "";
+ let letterCode = 'a'.charCodeAt(0);
+ for (let i = 0; i < BIG_LEN; i++) {
+ RQ_BIG[i] = letterCode;
+ expected += String.fromCharCode(letterCode);
+
+ if (letterCode < 'z'.charCodeAt(0)) {
+ letterCode++;
+ } else {
+ letterCode = 'a'.charCodeAt(0);
+ }
+ }
+ sock._rQ.set(RQ_BIG);
+ sock._rQlen = RQ_BIG.length;
+
+ const shifted = sock.rQshiftStr();
+
+ expect(shifted).to.be.equal(expected);
+ expect(sock.rQlen).to.equal(0);
+ });
+ });
+
+ describe('rQshiftBytes', function () {
+ it('should shift the given number of bytes of the receive queue and return an array', function () {
+ const bef_len = sock.rQlen;
+ const bef_rQi = sock.rQi;
+ const shifted = sock.rQshiftBytes(3);
+ expect(shifted).to.be.an.instanceof(Uint8Array);
+ expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3));
+ expect(sock.rQlen).to.equal(bef_len - 3);
+ });
+
+ it('should shift the entire rest of the queue off if no length is given', function () {
+ sock.rQshiftBytes();
+ expect(sock.rQlen).to.equal(0);
+ });
+ });
+
+ describe('rQslice', function () {
+ beforeEach(function () {
+ sock.rQi = 0;
+ });
+
+ it('should not modify the receive queue', function () {
+ const bef_len = sock.rQlen;
+ sock.rQslice(0, 2);
+ expect(sock.rQlen).to.equal(bef_len);
+ });
+
+ it('should return an array containing the given slice of the receive queue', function () {
+ const sl = sock.rQslice(0, 2);
+ expect(sl).to.be.an.instanceof(Uint8Array);
+ expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2));
+ });
+
+ it('should use the rest of the receive queue if no end is given', function () {
+ const sl = sock.rQslice(1);
+ expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
+ expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
+ });
+
+ it('should take the current rQi in to account', function () {
+ sock.rQi = 1;
+ expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
+ });
+ });
+
+ describe('rQwait', function () {
+ beforeEach(function () {
+ sock.rQi = 0;
+ });
+
+ it('should return true if there are not enough bytes in the receive queue', function () {
+ expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
+ });
+
+ it('should return false if there are enough bytes in the receive queue', function () {
+ expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
+ });
+
+ it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
+ sock.rQi = 5;
+ expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
+ expect(sock.rQi).to.equal(1);
+ });
+
+ it('should raise an error if we try to go back more than possible', function () {
+ sock.rQi = 5;
+ expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error);
+ });
+
+ it('should not reduce rQi if there are enough bytes', function () {
+ sock.rQi = 5;
+ sock.rQwait('hi', 1, 6);
+ expect(sock.rQi).to.equal(5);
+ });
+ });
+
+ describe('flush', function () {
+ beforeEach(function () {
+ sock._websocket = {
+ send: sinon.spy()
+ };
+ });
+
+ it('should actually send on the websocket', function () {
+ sock._websocket.bufferedAmount = 8;
+ sock._websocket.readyState = WebSocket.OPEN;
+ sock._sQ = new Uint8Array([1, 2, 3]);
+ sock._sQlen = 3;
+ const encoded = sock._encode_message();
+
+ sock.flush();
+ expect(sock._websocket.send).to.have.been.calledOnce;
+ expect(sock._websocket.send).to.have.been.calledWith(encoded);
+ });
+
+ it('should not call send if we do not have anything queued up', function () {
+ sock._sQlen = 0;
+ sock._websocket.bufferedAmount = 8;
+
+ sock.flush();
+
+ expect(sock._websocket.send).not.to.have.been.called;
+ });
+ });
+
+ describe('send', function () {
+ beforeEach(function () {
+ sock.flush = sinon.spy();
+ });
+
+ it('should add to the send queue', function () {
+ sock.send([1, 2, 3]);
+ const sq = sock.sQ;
+ expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3]));
+ });
+
+ it('should call flush', function () {
+ sock.send([1, 2, 3]);
+ expect(sock.flush).to.have.been.calledOnce;
+ });
+ });
+
+ describe('send_string', function () {
+ beforeEach(function () {
+ sock.send = sinon.spy();
+ });
+
+ it('should call send after converting the string to an array', function () {
+ sock.send_string("\x01\x02\x03");
+ expect(sock.send).to.have.been.calledWith([1, 2, 3]);
+ });
+ });
+ });
+
+ describe('lifecycle methods', function () {
+ let old_WS;
+ before(function () {
+ old_WS = WebSocket;
+ });
+
+ let sock;
+ beforeEach(function () {
+ sock = new Websock();
+ // eslint-disable-next-line no-global-assign
+ WebSocket = sinon.spy();
+ WebSocket.OPEN = old_WS.OPEN;
+ WebSocket.CONNECTING = old_WS.CONNECTING;
+ WebSocket.CLOSING = old_WS.CLOSING;
+ WebSocket.CLOSED = old_WS.CLOSED;
+
+ WebSocket.prototype.binaryType = 'arraybuffer';
+ });
+
+ describe('opening', function () {
+ it('should pick the correct protocols if none are given', function () {
+
+ });
+
+ it('should open the actual websocket', function () {
+ sock.open('ws://localhost:8675', 'binary');
+ expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary');
+ });
+
+ // it('should initialize the event handlers')?
+ });
+
+ describe('closing', function () {
+ beforeEach(function () {
+ sock.open('ws://');
+ sock._websocket.close = sinon.spy();
+ });
+
+ it('should close the actual websocket if it is open', function () {
+ sock._websocket.readyState = WebSocket.OPEN;
+ sock.close();
+ expect(sock._websocket.close).to.have.been.calledOnce;
+ });
+
+ it('should close the actual websocket if it is connecting', function () {
+ sock._websocket.readyState = WebSocket.CONNECTING;
+ sock.close();
+ expect(sock._websocket.close).to.have.been.calledOnce;
+ });
+
+ it('should not try to close the actual websocket if closing', function () {
+ sock._websocket.readyState = WebSocket.CLOSING;
+ sock.close();
+ expect(sock._websocket.close).not.to.have.been.called;
+ });
+
+ it('should not try to close the actual websocket if closed', function () {
+ sock._websocket.readyState = WebSocket.CLOSED;
+ sock.close();
+ expect(sock._websocket.close).not.to.have.been.called;
+ });
+
+ it('should reset onmessage to not call _recv_message', function () {
+ sinon.spy(sock, '_recv_message');
+ sock.close();
+ sock._websocket.onmessage(null);
+ try {
+ expect(sock._recv_message).not.to.have.been.called;
+ } finally {
+ sock._recv_message.restore();
+ }
+ });
+ });
+
+ describe('event handlers', function () {
+ beforeEach(function () {
+ sock._recv_message = sinon.spy();
+ sock.on('open', sinon.spy());
+ sock.on('close', sinon.spy());
+ sock.on('error', sinon.spy());
+ sock.open('ws://');
+ });
+
+ it('should call _recv_message on a message', function () {
+ sock._websocket.onmessage(null);
+ expect(sock._recv_message).to.have.been.calledOnce;
+ });
+
+ it('should call the open event handler on opening', function () {
+ sock._websocket.onopen();
+ expect(sock._eventHandlers.open).to.have.been.calledOnce;
+ });
+
+ it('should call the close event handler on closing', function () {
+ sock._websocket.onclose();
+ expect(sock._eventHandlers.close).to.have.been.calledOnce;
+ });
+
+ it('should call the error event handler on error', function () {
+ sock._websocket.onerror();
+ expect(sock._eventHandlers.error).to.have.been.calledOnce;
+ });
+ });
+
+ after(function () {
+ // eslint-disable-next-line no-global-assign
+ WebSocket = old_WS;
+ });
+ });
+
+ describe('WebSocket Receiving', function () {
+ let sock;
+ beforeEach(function () {
+ sock = new Websock();
+ sock._allocate_buffers();
+ });
+
+ it('should support adding binary Uint8Array data to the receive queue', function () {
+ const msg = { data: new Uint8Array([1, 2, 3]) };
+ sock._mode = 'binary';
+ sock._recv_message(msg);
+ expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
+ });
+
+ it('should call the message event handler if present', function () {
+ sock._eventHandlers.message = sinon.spy();
+ const msg = { data: new Uint8Array([1, 2, 3]).buffer };
+ sock._mode = 'binary';
+ sock._recv_message(msg);
+ expect(sock._eventHandlers.message).to.have.been.calledOnce;
+ });
+
+ it('should not call the message event handler if there is nothing in the receive queue', function () {
+ sock._eventHandlers.message = sinon.spy();
+ const msg = { data: new Uint8Array([]).buffer };
+ sock._mode = 'binary';
+ sock._recv_message(msg);
+ expect(sock._eventHandlers.message).not.to.have.been.called;
+ });
+
+ it('should compact the receive queue', function () {
+ // NB(sross): while this is an internal implementation detail, it's important to
+ // test, otherwise the receive queue could become very large very quickly
+ sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
+ sock._rQlen = 6;
+ sock.rQi = 6;
+ sock._rQmax = 3;
+ const msg = { data: new Uint8Array([1, 2, 3]).buffer };
+ sock._mode = 'binary';
+ sock._recv_message(msg);
+ expect(sock._rQlen).to.equal(3);
+ expect(sock.rQi).to.equal(0);
+ });
+
+ it('should automatically resize the receive queue if the incoming message is too large', function () {
+ sock._rQ = new Uint8Array(20);
+ sock._rQlen = 0;
+ sock.rQi = 0;
+ sock._rQbufferSize = 20;
+ sock._rQmax = 2;
+ const msg = { data: new Uint8Array(30).buffer };
+ sock._mode = 'binary';
+ sock._recv_message(msg);
+ expect(sock._rQlen).to.equal(30);
+ expect(sock.rQi).to.equal(0);
+ expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
+ });
+ });
+
+ describe('Data encoding', function () {
+ before(function () { FakeWebSocket.replace(); });
+ after(function () { FakeWebSocket.restore(); });
+
+ describe('as binary data', function () {
+ let sock;
+ beforeEach(function () {
+ sock = new Websock();
+ sock.open('ws://', 'binary');
+ sock._websocket._open();
+ });
+
+ it('should only send the send queue up to the send queue length', function () {
+ sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
+ sock._sQlen = 3;
+ const res = sock._encode_message();
+ expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
+ });
+
+ it('should properly pass the encoded data off to the actual WebSocket', function () {
+ sock.send([1, 2, 3]);
+ expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3]));
+ });
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/test.webutil.js b/systemvm/agent/noVNC/tests/test.webutil.js
new file mode 100644
index 0000000..72e1942
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/test.webutil.js
@@ -0,0 +1,184 @@
+/* jshint expr: true */
+
+const expect = chai.expect;
+
+import * as WebUtil from '../app/webutil.js';
+
+describe('WebUtil', function () {
+ "use strict";
+
+ describe('settings', function () {
+
+ describe('localStorage', function () {
+ let chrome = window.chrome;
+ before(function () {
+ chrome = window.chrome;
+ window.chrome = null;
+ });
+ after(function () {
+ window.chrome = chrome;
+ });
+
+ let origLocalStorage;
+ beforeEach(function () {
+ origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage");
+ if (origLocalStorage === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "localStorage", {value: {}});
+ if (window.localStorage.setItem !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.localStorage.setItem = sinon.stub();
+ window.localStorage.getItem = sinon.stub();
+ window.localStorage.removeItem = sinon.stub();
+
+ return WebUtil.initSettings();
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "localStorage", origLocalStorage);
+ });
+
+ describe('writeSetting', function () {
+ it('should save the setting value to local storage', function () {
+ WebUtil.writeSetting('test', 'value');
+ expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ });
+ });
+
+ describe('setSetting', function () {
+ it('should update the setting but not save to local storage', function () {
+ WebUtil.setSetting('test', 'value');
+ expect(window.localStorage.setItem).to.not.have.been.called;
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ });
+ });
+
+ describe('readSetting', function () {
+ it('should read the setting value from local storage', function () {
+ localStorage.getItem.returns('value');
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ });
+
+ it('should return the default value when not in local storage', function () {
+ expect(WebUtil.readSetting('test', 'default')).to.equal('default');
+ });
+
+ it('should return the cached value even if local storage changed', function () {
+ localStorage.getItem.returns('value');
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ localStorage.getItem.returns('something else');
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ });
+
+ it('should cache the value even if it is not initially in local storage', function () {
+ expect(WebUtil.readSetting('test')).to.be.null;
+ localStorage.getItem.returns('value');
+ expect(WebUtil.readSetting('test')).to.be.null;
+ });
+
+ it('should return the default value always if the first read was not in local storage', function () {
+ expect(WebUtil.readSetting('test', 'default')).to.equal('default');
+ localStorage.getItem.returns('value');
+ expect(WebUtil.readSetting('test', 'another default')).to.equal('another default');
+ });
+
+ it('should return the last local written value', function () {
+ localStorage.getItem.returns('value');
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ WebUtil.writeSetting('test', 'something else');
+ expect(WebUtil.readSetting('test')).to.equal('something else');
+ });
+ });
+
+ // this doesn't appear to be used anywhere
+ describe('eraseSetting', function () {
+ it('should remove the setting from local storage', function () {
+ WebUtil.eraseSetting('test');
+ expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
+ });
+ });
+ });
+
+ describe('chrome.storage', function () {
+ let chrome = window.chrome;
+ let settings = {};
+ before(function () {
+ chrome = window.chrome;
+ window.chrome = {
+ storage: {
+ sync: {
+ get(cb) { cb(settings); },
+ set() {},
+ remove() {}
+ }
+ }
+ };
+ });
+ after(function () {
+ window.chrome = chrome;
+ });
+
+ const csSandbox = sinon.createSandbox();
+
+ beforeEach(function () {
+ settings = {};
+ csSandbox.spy(window.chrome.storage.sync, 'set');
+ csSandbox.spy(window.chrome.storage.sync, 'remove');
+ return WebUtil.initSettings();
+ });
+ afterEach(function () {
+ csSandbox.restore();
+ });
+
+ describe('writeSetting', function () {
+ it('should save the setting value to chrome storage', function () {
+ WebUtil.writeSetting('test', 'value');
+ expect(window.chrome.storage.sync.set).to.have.been.calledWithExactly(sinon.match({ test: 'value' }));
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ });
+ });
+
+ describe('setSetting', function () {
+ it('should update the setting but not save to chrome storage', function () {
+ WebUtil.setSetting('test', 'value');
+ expect(window.chrome.storage.sync.set).to.not.have.been.called;
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ });
+ });
+
+ describe('readSetting', function () {
+ it('should read the setting value from chrome storage', function () {
+ settings.test = 'value';
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ });
+
+ it('should return the default value when not in chrome storage', function () {
+ expect(WebUtil.readSetting('test', 'default')).to.equal('default');
+ });
+
+ it('should return the last local written value', function () {
+ settings.test = 'value';
+ expect(WebUtil.readSetting('test')).to.equal('value');
+ WebUtil.writeSetting('test', 'something else');
+ expect(WebUtil.readSetting('test')).to.equal('something else');
+ });
+ });
+
+ // this doesn't appear to be used anywhere
+ describe('eraseSetting', function () {
+ it('should remove the setting from chrome storage', function () {
+ WebUtil.eraseSetting('test');
+ expect(window.chrome.storage.sync.remove).to.have.been.calledWithExactly('test');
+ });
+ });
+ });
+ });
+});
diff --git a/systemvm/agent/noVNC/tests/vnc_playback.html b/systemvm/agent/noVNC/tests/vnc_playback.html
new file mode 100644
index 0000000..4fd7465
--- /dev/null
+++ b/systemvm/agent/noVNC/tests/vnc_playback.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>VNC Playback</title>
+ <!-- promise polyfills promises for IE11 -->
+ <script src="../vendor/promise.js"></script>
+ <!-- ES2015/ES6 modules polyfill -->
+ <script type="module">
+ window._noVNC_has_module_support = true;
+ </script>
+ <script>
+ window.addEventListener("load", function() {
+ if (window._noVNC_has_module_support) return;
+ var loader = document.createElement("script");
+ loader.src = "../vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
+ document.head.appendChild(loader);
+ });
+ </script>
+ <!-- actual script modules -->
+ <script type="module" src="./playback-ui.js"></script>
+ </head>
+ <body>
+
+ Iterations: <input id='iterations'>
+ Perftest:<input type='radio' id='mode1' name='mode' checked>
+ Realtime:<input type='radio' id='mode2' name='mode'>
+
+ <input id='startButton' type='button' value='Start' disabled>
+
+ <br><br>
+
+ Results:<br>
+ <textarea id="messages" cols=80 rows=25></textarea>
+
+ <br><br>
+
+ <div id="VNC_screen">
+ <div id="VNC_status">Loading</div>
+ </div>
+
+ <script type="module" src="./playback-ui.js"></script>
+ </body>
+</html>
diff --git a/systemvm/agent/noVNC/utils/.eslintrc b/systemvm/agent/noVNC/utils/.eslintrc
new file mode 100644
index 0000000..b7dc129
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/.eslintrc
@@ -0,0 +1,8 @@
+{
+ "env": {
+ "node": true
+ },
+ "rules": {
+ "no-console": 0
+ }
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/utils/README.md b/systemvm/agent/noVNC/utils/README.md
new file mode 100644
index 0000000..32582e6
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/README.md
@@ -0,0 +1,14 @@
+## WebSockets Proxy/Bridge
+
+Websockify has been forked out into its own project. `launch.sh` wil
+automatically download it here if it is not already present and not
+installed as system-wide.
+
+For more detailed description and usage information please refer to
+the [websockify README](https://github.com/novnc/websockify/blob/master/README.md).
+
+The other versions of websockify (C, Node.js) and the associated test
+programs have been moved to
+[websockify](https://github.com/novnc/websockify). Websockify was
+formerly named wsproxy.
+
diff --git a/systemvm/agent/noVNC/utils/b64-to-binary.pl b/systemvm/agent/noVNC/utils/b64-to-binary.pl
new file mode 100755
index 0000000..280e28c
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/b64-to-binary.pl
@@ -0,0 +1,17 @@
+#!/usr/bin/env perl
+use MIME::Base64;
+
+for (<>) {
+ unless (/^'([{}])(\d+)\1(.+?)',$/) {
+ print;
+ next;
+ }
+
+ my ($dir, $amt, $b64) = ($1, $2, $3);
+
+ my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`";
+
+ my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded);
+
+ print "'${dir}${amt}${dir}${decoded_escaped}',\n";
+}
diff --git a/systemvm/agent/noVNC/utils/genkeysymdef.js b/systemvm/agent/noVNC/utils/genkeysymdef.js
new file mode 100755
index 0000000..d21773f
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/genkeysymdef.js
@@ -0,0 +1,127 @@
+#!/usr/bin/env node
+/*
+ * genkeysymdef: X11 keysymdef.h to JavaScript converter
+ * Copyright (C) 2018 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ */
+
+"use strict";
+
+const fs = require('fs');
+
+let show_help = process.argv.length === 2;
+let filename;
+
+for (let i = 2; i < process.argv.length; ++i) {
+ switch (process.argv[i]) {
+ case "--help":
+ case "-h":
+ show_help = true;
+ break;
+ case "--file":
+ case "-f":
+ default:
+ filename = process.argv[i];
+ }
+}
+
+if (!filename) {
+ show_help = true;
+ console.log("Error: No filename specified\n");
+}
+
+if (show_help) {
+ console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
+ console.log("Usage: node parse.js [options] filename:");
+ console.log(" -h [ --help ] Produce this help message");
+ console.log(" filename The keysymdef.h file to parse");
+ process.exit(0);
+}
+
+const buf = fs.readFileSync(filename);
+const str = buf.toString('utf8');
+
+const re = /^#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
+
+const arr = str.split('\n');
+
+const codepoints = {};
+
+for (let i = 0; i < arr.length; ++i) {
+ const result = re.exec(arr[i]);
+ if (result) {
+ const keyname = result[1];
+ const keysym = parseInt(result[2], 16);
+ const remainder = result[3];
+
+ const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
+ if (unicodeRes) {
+ const unicode = parseInt(unicodeRes[1], 16);
+ // The first entry is the preferred one
+ if (!codepoints[unicode]) {
+ codepoints[unicode] = { keysym: keysym, name: keyname };
+ }
+ }
+ }
+}
+
+let out =
+"/*\n" +
+" * Mapping from Unicode codepoints to X11/RFB keysyms\n" +
+" *\n" +
+" * This file was automatically generated from keysymdef.h\n" +
+" * DO NOT EDIT!\n" +
+" */\n" +
+"\n" +
+"/* Functions at the bottom */\n" +
+"\n" +
+"const codepoints = {\n";
+
+function toHex(num) {
+ let s = num.toString(16);
+ if (s.length < 4) {
+ s = ("0000" + s).slice(-4);
+ }
+ return "0x" + s;
+}
+
+for (let codepoint in codepoints) {
+ codepoint = parseInt(codepoint);
+
+ // Latin-1?
+ if ((codepoint >= 0x20) && (codepoint <= 0xff)) {
+ continue;
+ }
+
+ // Handled by the general Unicode mapping?
+ if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) {
+ continue;
+ }
+
+ out += " " + toHex(codepoint) + ": " +
+ toHex(codepoints[codepoint].keysym) +
+ ", // XK_" + codepoints[codepoint].name + "\n";
+}
+
+out +=
+"};\n" +
+"\n" +
+"export default {\n" +
+" lookup(u) {\n" +
+" // Latin-1 is one-to-one mapping\n" +
+" if ((u >= 0x20) && (u <= 0xff)) {\n" +
+" return u;\n" +
+" }\n" +
+"\n" +
+" // Lookup table (fairly random)\n" +
+" const keysym = codepoints[u];\n" +
+" if (keysym !== undefined) {\n" +
+" return keysym;\n" +
+" }\n" +
+"\n" +
+" // General mapping as final fallback\n" +
+" return 0x01000000 | u;\n" +
+" },\n" +
+"};";
+
+console.log(out);
diff --git a/systemvm/agent/noVNC/utils/img2js.py b/systemvm/agent/noVNC/utils/img2js.py
new file mode 100755
index 0000000..ceab6bf
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/img2js.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+#
+# Convert image to Javascript compatible base64 Data URI
+# Copyright (C) 2018 The noVNC Authors
+# Licensed under MPL 2.0 (see docs/LICENSE.MPL-2.0)
+#
+
+import sys, base64
+
+try:
+ from PIL import Image
+except:
+ print "python PIL module required (python-imaging package)"
+ sys.exit(1)
+
+
+if len(sys.argv) < 3:
+ print "Usage: %s IMAGE JS_VARIABLE" % sys.argv[0]
+ sys.exit(1)
+
+fname = sys.argv[1]
+var = sys.argv[2]
+
+ext = fname.lower().split('.')[-1]
+if ext == "png": mime = "image/png"
+elif ext in ["jpg", "jpeg"]: mime = "image/jpeg"
+elif ext == "gif": mime = "image/gif"
+else:
+ print "Only PNG, JPEG and GIF images are supported"
+ sys.exit(1)
+uri = "data:%s;base64," % mime
+
+im = Image.open(fname)
+w, h = im.size
+
+raw = open(fname).read()
+
+print '%s = {"width": %s, "height": %s, "data": "%s%s"};' % (
+ var, w, h, uri, base64.b64encode(raw))
diff --git a/systemvm/agent/noVNC/utils/json2graph.py b/systemvm/agent/noVNC/utils/json2graph.py
new file mode 100755
index 0000000..bdaeecc
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/json2graph.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+
+'''
+Use matplotlib to generate performance charts
+Copyright (C) 2018 The noVNC Authors
+Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
+'''
+
+# a bar plot with errorbars
+import sys, json
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.font_manager import FontProperties
+
+def usage():
+ print "%s json_file level1 level2 level3 [legend_height]\n\n" % sys.argv[0]
+ print "Description:\n"
+ print "level1, level2, and level3 are one each of the following:\n";
+ print " select=ITEM - select only ITEM at this level";
+ print " bar - each item on this level becomes a graph bar";
+ print " group - items on this level become groups of bars";
+ print "\n";
+ print "json_file is a file containing json data in the following format:\n"
+ print ' {';
+ print ' "conf": {';
+ print ' "order_l1": [';
+ print ' "level1_label1",';
+ print ' "level1_label2",';
+ print ' ...';
+ print ' ],';
+ print ' "order_l2": [';
+ print ' "level2_label1",';
+ print ' "level2_label2",';
+ print ' ...';
+ print ' ],';
+ print ' "order_l3": [';
+ print ' "level3_label1",';
+ print ' "level3_label2",';
+ print ' ...';
+ print ' ]';
+ print ' },';
+ print ' "stats": {';
+ print ' "level1_label1": {';
+ print ' "level2_label1": {';
+ print ' "level3_label1": [val1, val2, val3],';
+ print ' "level3_label2": [val1, val2, val3],';
+ print ' ...';
+ print ' },';
+ print ' "level2_label2": {';
+ print ' ...';
+ print ' },';
+ print ' },';
+ print ' "level1_label2": {';
+ print ' ...';
+ print ' },';
+ print ' ...';
+ print ' },';
+ print ' }';
+ sys.exit(2)
+
+def error(msg):
+ print msg
+ sys.exit(1)
+
+
+#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100',
+# '#800000', '#805100', '#013075', '#007900']
+colors = ['#ff0000', '#00ff00', '#0000ff',
+ '#dddd00', '#dd00dd', '#00dddd',
+ '#dd6622', '#dd2266', '#66dd22',
+ '#8844dd', '#44dd88', '#4488dd']
+
+if len(sys.argv) < 5:
+ usage()
+
+filename = sys.argv[1]
+L1 = sys.argv[2]
+L2 = sys.argv[3]
+L3 = sys.argv[4]
+if len(sys.argv) > 5:
+ legendHeight = float(sys.argv[5])
+else:
+ legendHeight = 0.75
+
+# Load the JSON data from the file
+data = json.loads(file(filename).read())
+conf = data['conf']
+stats = data['stats']
+
+# Sanity check data hierarchy
+if len(conf['order_l1']) != len(stats.keys()):
+ error("conf.order_l1 does not match stats level 1")
+for l1 in stats.keys():
+ if len(conf['order_l2']) != len(stats[l1].keys()):
+ error("conf.order_l2 does not match stats level 2 for %s" % l1)
+ if conf['order_l1'].count(l1) < 1:
+ error("%s not found in conf.order_l1" % l1)
+ for l2 in stats[l1].keys():
+ if len(conf['order_l3']) != len(stats[l1][l2].keys()):
+ error("conf.order_l3 does not match stats level 3")
+ if conf['order_l2'].count(l2) < 1:
+ error("%s not found in conf.order_l2" % l2)
+ for l3 in stats[l1][l2].keys():
+ if conf['order_l3'].count(l3) < 1:
+ error("%s not found in conf.order_l3" % l3)
+
+#
+# Generate the data based on the level specifications
+#
+bar_labels = None
+group_labels = None
+bar_vals = []
+bar_sdvs = []
+if L3.startswith("select="):
+ select_label = l3 = L3.split("=")[1]
+ bar_labels = conf['order_l1']
+ group_labels = conf['order_l2']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l1 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l2 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+elif L2.startswith("select="):
+ select_label = l2 = L2.split("=")[1]
+ bar_labels = conf['order_l1']
+ group_labels = conf['order_l3']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l1 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l3 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+elif L1.startswith("select="):
+ select_label = l1 = L1.split("=")[1]
+ bar_labels = conf['order_l2']
+ group_labels = conf['order_l3']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l2 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l3 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+else:
+ usage()
+
+# If group is before bar then flip (zip) the data
+if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"):
+ bar_labels, group_labels = group_labels, bar_labels
+ bar_vals = zip(*bar_vals)
+ bar_sdvs = zip(*bar_sdvs)
+
+print "bar_vals:", bar_vals
+
+#
+# Now render the bar graph
+#
+ind = np.arange(len(group_labels)) # the x locations for the groups
+width = 0.8 * (1.0/len(bar_labels)) # the width of the bars
+
+fig = plt.figure(figsize=(10,6), dpi=80)
+plot = fig.add_subplot(1, 1, 1)
+
+rects = []
+for i in range(len(bar_vals)):
+ rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i],
+ yerr=bar_sdvs[i], align='center'))
+
+# add some
+plot.set_ylabel('Milliseconds (less is better)')
+plot.set_title("Javascript array test: %s" % select_label)
+plot.set_xticks(ind+width)
+plot.set_xticklabels( group_labels )
+
+fontP = FontProperties()
+fontP.set_size('small')
+plot.legend( [r[0] for r in rects], bar_labels, prop=fontP,
+ loc = 'center right', bbox_to_anchor = (1.0, legendHeight))
+
+def autolabel(rects):
+ # attach some text labels
+ for rect in rects:
+ height = rect.get_height()
+ if np.isnan(height):
+ height = 0.0
+ plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height),
+ ha='center', va='bottom', size='7')
+
+for rect in rects:
+ autolabel(rect)
+
+# Adjust axis sizes
+axis = list(plot.axis())
+axis[0] = -width # Make sure left side has enough for bar
+#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits
+axis[2] = 0 # Make y-axis start at 0
+axis[3] = axis[3] * 1.10 # Add 10% to the top
+plot.axis(axis)
+
+plt.show()
diff --git a/systemvm/agent/noVNC/utils/launch.sh b/systemvm/agent/noVNC/utils/launch.sh
new file mode 100755
index 0000000..162607e
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/launch.sh
@@ -0,0 +1,169 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2018 The noVNC Authors
+# Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+
+usage() {
+ if [ "$*" ]; then
+ echo "$*"
+ echo
+ fi
+ echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]"
+ echo
+ echo "Starts the WebSockets proxy and a mini-webserver and "
+ echo "provides a cut-and-paste URL to go to."
+ echo
+ echo " --listen PORT Port for proxy/webserver to listen on"
+ echo " Default: 6080"
+ echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
+ echo " Default: localhost:5900"
+ echo " --cert CERT Path to combined cert/key file"
+ echo " Default: self.pem"
+ echo " --web WEB Path to web files (e.g. vnc.html)"
+ echo " Default: ./"
+ echo " --ssl-only Disable non-https connections."
+ echo " "
+ echo " --record FILE Record traffic to FILE.session.js"
+ echo " "
+ exit 2
+}
+
+NAME="$(basename $0)"
+REAL_NAME="$(readlink -f $0)"
+HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
+PORT="6080"
+VNC_DEST="localhost:5900"
+CERT=""
+WEB=""
+proxy_pid=""
+SSLONLY=""
+RECORD_ARG=""
+
+die() {
+ echo "$*"
+ exit 1
+}
+
+cleanup() {
+ trap - TERM QUIT INT EXIT
+ trap "true" CHLD # Ignore cleanup messages
+ echo
+ if [ -n "${proxy_pid}" ]; then
+ echo "Terminating WebSockets proxy (${proxy_pid})"
+ kill ${proxy_pid}
+ fi
+}
+
+# Process Arguments
+
+# Arguments that only apply to chrooter itself
+while [ "$*" ]; do
+ param=$1; shift; OPTARG=$1
+ case $param in
+ --listen) PORT="${OPTARG}"; shift ;;
+ --vnc) VNC_DEST="${OPTARG}"; shift ;;
+ --cert) CERT="${OPTARG}"; shift ;;
+ --web) WEB="${OPTARG}"; shift ;;
+ --ssl-only) SSLONLY="--ssl-only" ;;
+ --record) RECORD_ARG="--record ${OPTARG}"; shift ;;
+ -h|--help) usage ;;
+ -*) usage "Unknown chrooter option: ${param}" ;;
+ *) break ;;
+ esac
+done
+
+# Sanity checks
+if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then
+ exec 7<&-
+ exec 7>&-
+ die "Port ${PORT} in use. Try --listen PORT"
+else
+ exec 7<&-
+ exec 7>&-
+fi
+
+trap "cleanup" TERM QUIT INT EXIT
+
+# Find vnc.html
+if [ -n "${WEB}" ]; then
+ if [ ! -e "${WEB}/vnc.html" ]; then
+ die "Could not find ${WEB}/vnc.html"
+ fi
+elif [ -e "$(pwd)/vnc.html" ]; then
+ WEB=$(pwd)
+elif [ -e "${HERE}/../vnc.html" ]; then
+ WEB=${HERE}/../
+elif [ -e "${HERE}/vnc.html" ]; then
+ WEB=${HERE}
+elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
+ WEB=${HERE}/../share/novnc/
+else
+ die "Could not find vnc.html"
+fi
+
+# Find self.pem
+if [ -n "${CERT}" ]; then
+ if [ ! -e "${CERT}" ]; then
+ die "Could not find ${CERT}"
+ fi
+elif [ -e "$(pwd)/self.pem" ]; then
+ CERT="$(pwd)/self.pem"
+elif [ -e "${HERE}/../self.pem" ]; then
+ CERT="${HERE}/../self.pem"
+elif [ -e "${HERE}/self.pem" ]; then
+ CERT="${HERE}/self.pem"
+else
+ echo "Warning: could not find self.pem"
+fi
+
+# try to find websockify (prefer local, try global, then download local)
+if [[ -e ${HERE}/websockify ]]; then
+ WEBSOCKIFY=${HERE}/websockify/run
+
+ if [[ ! -x $WEBSOCKIFY ]]; then
+ echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable."
+ echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify."
+ exit 1
+ fi
+
+ echo "Using local websockify at $WEBSOCKIFY"
+else
+ WEBSOCKIFY=$(which websockify 2>/dev/null)
+
+ if [[ $? -ne 0 ]]; then
+ echo "No installed websockify, attempting to clone websockify..."
+ WEBSOCKIFY=${HERE}/websockify/run
+ git clone https://github.com/novnc/websockify ${HERE}/websockify
+
+ if [[ ! -e $WEBSOCKIFY ]]; then
+ echo "Unable to locate ${HERE}/websockify/run after downloading"
+ exit 1
+ fi
+
+ echo "Using local websockify at $WEBSOCKIFY"
+ else
+ echo "Using installed websockify at $WEBSOCKIFY"
+ fi
+fi
+
+echo "Starting webserver and WebSockets proxy on port ${PORT}"
+#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
+${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} ${RECORD_ARG} &
+proxy_pid="$!"
+sleep 1
+if ! ps -p ${proxy_pid} >/dev/null; then
+ proxy_pid=
+ echo "Failed to start WebSockets proxy"
+ exit 1
+fi
+
+echo -e "\n\nNavigate to this URL:\n"
+if [ "x$SSLONLY" == "x" ]; then
+ echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
+else
+ echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
+fi
+
+echo -e "Press Ctrl-C to exit\n\n"
+
+wait ${proxy_pid}
diff --git a/systemvm/agent/noVNC/utils/u2x11 b/systemvm/agent/noVNC/utils/u2x11
new file mode 100755
index 0000000..fd3e4ba
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/u2x11
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+#
+# Convert "U+..." commented entries in /usr/include/X11/keysymdef.h
+# into JavaScript for use by noVNC. Note this is likely to produce
+# a few duplicate properties with clashing values, that will need
+# resolving manually.
+#
+# Colin Dean <colin@xvpsource.org>
+#
+
+regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$"
+echo "unicodeTable = {"
+while read line; do
+ if echo "${line}" | egrep -qs "${regex}"; then
+
+ x11=$(echo "${line}" | sed -r "s/${regex}/\1/")
+ vnc=$(echo "${line}" | sed -r "s/${regex}/\2/")
+
+ if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then
+ : # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping
+ else
+ # note 1-to-1 is possible (e.g. for Euro symbol, U+20AC)
+ echo " 0x${vnc} : 0x${x11},"
+ fi
+ fi
+done < /usr/include/X11/keysymdef.h | uniq
+echo "};"
+
diff --git a/systemvm/agent/noVNC/utils/use_require.js b/systemvm/agent/noVNC/utils/use_require.js
new file mode 100755
index 0000000..2487927
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/use_require.js
@@ -0,0 +1,313 @@
+#!/usr/bin/env node
+
+const path = require('path');
+const program = require('commander');
+const fs = require('fs');
+const fse = require('fs-extra');
+const babel = require('babel-core');
+
+const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
+
+program
+ .option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`)
+ .option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
+ .option('--with-app', 'process app files as well as core files')
+ .option('--only-legacy', 'only output legacy files (no ES6 modules) for the app')
+ .option('--clean', 'clear the lib folder before building')
+ .parse(process.argv);
+
+// the various important paths
+const paths = {
+ main: path.resolve(__dirname, '..'),
+ core: path.resolve(__dirname, '..', 'core'),
+ app: path.resolve(__dirname, '..', 'app'),
+ vendor: path.resolve(__dirname, '..', 'vendor'),
+ out_dir_base: path.resolve(__dirname, '..', 'build'),
+ lib_dir_base: path.resolve(__dirname, '..', 'lib'),
+};
+
+const no_copy_files = new Set([
+ // skip these -- they don't belong in the processed application
+ path.join(paths.vendor, 'sinon.js'),
+ path.join(paths.vendor, 'browser-es-module-loader'),
+ path.join(paths.vendor, 'promise.js'),
+ path.join(paths.app, 'images', 'icons', 'Makefile'),
+]);
+
+const no_transform_files = new Set([
+ // don't transform this -- we want it imported as-is to properly catch loading errors
+ path.join(paths.app, 'error-handler.js'),
+]);
+
+no_copy_files.forEach(file => no_transform_files.add(file));
+
+// util.promisify requires Node.js 8.x, so we have our own
+function promisify(original) {
+ return function promise_wrap() {
+ const args = Array.prototype.slice.call(arguments);
+ return new Promise((resolve, reject) => {
+ original.apply(this, args.concat((err, value) => {
+ if (err) return reject(err);
+ resolve(value);
+ }));
+ });
+ };
+}
+
+const readFile = promisify(fs.readFile);
+const writeFile = promisify(fs.writeFile);
+
+const readdir = promisify(fs.readdir);
+const lstat = promisify(fs.lstat);
+
+const copy = promisify(fse.copy);
+const unlink = promisify(fse.unlink);
+const ensureDir = promisify(fse.ensureDir);
+const rmdir = promisify(fse.rmdir);
+
+const babelTransformFile = promisify(babel.transformFile);
+
+// walkDir *recursively* walks directories trees,
+// calling the callback for all normal files found.
+function walkDir(base_path, cb, filter) {
+ return readdir(base_path)
+ .then((files) => {
+ const paths = files.map(filename => path.join(base_path, filename));
+ return Promise.all(paths.map(filepath => lstat(filepath)
+ .then((stats) => {
+ if (filter !== undefined && !filter(filepath, stats)) return;
+
+ if (stats.isSymbolicLink()) return;
+ if (stats.isFile()) return cb(filepath);
+ if (stats.isDirectory()) return walkDir(filepath, cb, filter);
+ })));
+ });
+}
+
+function transform_html(legacy_scripts, only_legacy) {
+ // write out the modified vnc.html file that works with the bundle
+ const src_html_path = path.resolve(__dirname, '..', 'vnc.html');
+ const out_html_path = path.resolve(paths.out_dir_base, 'vnc.html');
+ return readFile(src_html_path)
+ .then((contents_raw) => {
+ let contents = contents_raw.toString();
+
+ const start_marker = '<!-- begin scripts -->\n';
+ const end_marker = '<!-- end scripts -->';
+ const start_ind = contents.indexOf(start_marker) + start_marker.length;
+ const end_ind = contents.indexOf(end_marker, start_ind);
+
+ let new_script = '';
+
+ if (only_legacy) {
+ // Only legacy version, so include things directly
+ for (let i = 0;i < legacy_scripts.length;i++) {
+ new_script += ` <script src="${legacy_scripts[i]}"></script>\n`;
+ }
+ } else {
+ // Otherwise detect if it's a modern browser and select
+ // variant accordingly
+ new_script += `\
+ <script type="module">\n\
+ window._noVNC_has_module_support = true;\n\
+ </script>\n\
+ <script>\n\
+ window.addEventListener("load", function() {\n\
+ if (window._noVNC_has_module_support) return;\n\
+ let legacy_scripts = ${JSON.stringify(legacy_scripts)};\n\
+ for (let i = 0;i < legacy_scripts.length;i++) {\n\
+ let script = document.createElement("script");\n\
+ script.src = legacy_scripts[i];\n\
+ script.async = false;\n\
+ document.head.appendChild(script);\n\
+ }\n\
+ });\n\
+ </script>\n`;
+
+ // Original, ES6 modules
+ new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
+ }
+
+ contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind);
+
+ return contents;
+ })
+ .then((contents) => {
+ console.log(`Writing ${out_html_path}`);
+ return writeFile(out_html_path, contents);
+ });
+}
+
+function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
+ if (!import_format) {
+ throw new Error("you must specify an import format to generate compiled noVNC libraries");
+ } else if (!SUPPORTED_FORMATS.has(import_format)) {
+ throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
+ }
+
+ // NB: we need to make a copy of babel_opts, since babel sets some defaults on it
+ const babel_opts = () => ({
+ plugins: [`transform-es2015-modules-${import_format}`],
+ presets: ['es2015'],
+ ast: false,
+ sourceMaps: source_maps,
+ });
+
+ // No point in duplicate files without the app, so force only converted files
+ if (!with_app_dir) {
+ only_legacy = true;
+ }
+
+ let in_path;
+ let out_path_base;
+ if (with_app_dir) {
+ out_path_base = paths.out_dir_base;
+ in_path = paths.main;
+ } else {
+ out_path_base = paths.lib_dir_base;
+ }
+ const legacy_path_base = only_legacy ? out_path_base : path.join(out_path_base, 'legacy');
+
+ fse.ensureDirSync(out_path_base);
+
+ const helpers = require('./use_require_helpers');
+ const helper = helpers[import_format];
+
+ const outFiles = [];
+
+ const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve()
+ .then(() => {
+ if (no_copy_files.has(filename)) return;
+
+ const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
+ const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
+
+ if (path.extname(filename) !== '.js') {
+ if (!js_only) {
+ console.log(`Writing ${out_path}`);
+ return copy(filename, out_path);
+ }
+ return; // skip non-javascript files
+ }
+
+ return Promise.resolve()
+ .then(() => {
+ if (only_legacy && !no_transform_files.has(filename)) {
+ return;
+ }
+ return ensureDir(path.dirname(out_path))
+ .then(() => {
+ console.log(`Writing ${out_path}`);
+ return copy(filename, out_path);
+ });
+ })
+ .then(() => ensureDir(path.dirname(legacy_path)))
+ .then(() => {
+ if (no_transform_files.has(filename)) {
+ return;
+ }
+
+ const opts = babel_opts();
+ if (helper && helpers.optionsOverride) {
+ helper.optionsOverride(opts);
+ }
+ // Adjust for the fact that we move the core files relative
+ // to the vendor directory
+ if (vendor_rewrite) {
+ opts.plugins.push(["import-redirect",
+ {"root": legacy_path_base,
+ "redirect": { "vendor/(.+)": "./vendor/$1"}}]);
+ }
+
+ return babelTransformFile(filename, opts)
+ .then((res) => {
+ console.log(`Writing ${legacy_path}`);
+ const {map} = res;
+ let {code} = res;
+ if (source_maps === true) {
+ // append URL for external source map
+ code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`;
+ }
+ outFiles.push(`${legacy_path}`);
+ return writeFile(legacy_path, code)
+ .then(() => {
+ if (source_maps === true || source_maps === 'both') {
+ console.log(` and ${legacy_path}.map`);
+ outFiles.push(`${legacy_path}.map`);
+ return writeFile(`${legacy_path}.map`, JSON.stringify(map));
+ }
+ });
+ });
+ });
+ });
+
+ if (with_app_dir && helper && helper.noCopyOverride) {
+ helper.noCopyOverride(paths, no_copy_files);
+ }
+
+ Promise.resolve()
+ .then(() => {
+ const handler = handleDir.bind(null, true, false, in_path || paths.main);
+ const filter = (filename, stats) => !no_copy_files.has(filename);
+ return walkDir(paths.vendor, handler, filter);
+ })
+ .then(() => {
+ const handler = handleDir.bind(null, true, !in_path, in_path || paths.core);
+ const filter = (filename, stats) => !no_copy_files.has(filename);
+ return walkDir(paths.core, handler, filter);
+ })
+ .then(() => {
+ if (!with_app_dir) return;
+ const handler = handleDir.bind(null, false, false, in_path);
+ const filter = (filename, stats) => !no_copy_files.has(filename);
+ return walkDir(paths.app, handler, filter);
+ })
+ .then(() => {
+ if (!with_app_dir) return;
+
+ if (!helper || !helper.appWriter) {
+ throw new Error(`Unable to generate app for the ${import_format} format!`);
+ }
+
+ const out_app_path = path.join(legacy_path_base, 'app.js');
+ console.log(`Writing ${out_app_path}`);
+ return helper.appWriter(out_path_base, legacy_path_base, out_app_path)
+ .then((extra_scripts) => {
+ const rel_app_path = path.relative(out_path_base, out_app_path);
+ const legacy_scripts = extra_scripts.concat([rel_app_path]);
+ transform_html(legacy_scripts, only_legacy);
+ })
+ .then(() => {
+ if (!helper.removeModules) return;
+ console.log(`Cleaning up temporary files...`);
+ return Promise.all(outFiles.map((filepath) => {
+ unlink(filepath)
+ .then(() => {
+ // Try to clean up any empty directories if this
+ // was the last file in there
+ const rmdir_r = dir =>
+ rmdir(dir)
+ .then(() => rmdir_r(path.dirname(dir)))
+ .catch(() => {
+ // Assume the error was ENOTEMPTY and ignore it
+ });
+ return rmdir_r(path.dirname(filepath));
+ });
+ }));
+ });
+ })
+ .catch((err) => {
+ console.error(`Failure converting modules: ${err}`);
+ process.exit(1);
+ });
+}
+
+if (program.clean) {
+ console.log(`Removing ${paths.lib_dir_base}`);
+ fse.removeSync(paths.lib_dir_base);
+
+ console.log(`Removing ${paths.out_dir_base}`);
+ fse.removeSync(paths.out_dir_base);
+}
+
+make_lib_files(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy);
diff --git a/systemvm/agent/noVNC/utils/use_require_helpers.js b/systemvm/agent/noVNC/utils/use_require_helpers.js
new file mode 100644
index 0000000..a4f99c7
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/use_require_helpers.js
@@ -0,0 +1,76 @@
+// writes helpers require for vnc.html (they should output app.js)
+const fs = require('fs');
+const path = require('path');
+
+// util.promisify requires Node.js 8.x, so we have our own
+function promisify(original) {
+ return function promise_wrap() {
+ const args = Array.prototype.slice.call(arguments);
+ return new Promise((resolve, reject) => {
+ original.apply(this, args.concat((err, value) => {
+ if (err) return reject(err);
+ resolve(value);
+ }));
+ });
+ };
+}
+
+const writeFile = promisify(fs.writeFile);
+
+module.exports = {
+ 'amd': {
+ appWriter: (base_out_path, script_base_path, out_path) => {
+ // setup for requirejs
+ const ui_path = path.relative(base_out_path,
+ path.join(script_base_path, 'app', 'ui'));
+ return writeFile(out_path, `requirejs(["${ui_path}"], (ui) => {});`)
+ .then(() => {
+ console.log(`Please place RequireJS in ${path.join(script_base_path, 'require.js')}`);
+ const require_path = path.relative(base_out_path,
+ path.join(script_base_path, 'require.js'));
+ return [ require_path ];
+ });
+ },
+ noCopyOverride: () => {},
+ },
+ 'commonjs': {
+ optionsOverride: (opts) => {
+ // CommonJS supports properly shifting the default export to work as normal
+ opts.plugins.unshift("add-module-exports");
+ },
+ appWriter: (base_out_path, script_base_path, out_path) => {
+ const browserify = require('browserify');
+ const b = browserify(path.join(script_base_path, 'app/ui.js'), {});
+ return promisify(b.bundle).call(b)
+ .then(buf => writeFile(out_path, buf))
+ .then(() => []);
+ },
+ noCopyOverride: () => {},
+ removeModules: true,
+ },
+ 'systemjs': {
+ appWriter: (base_out_path, script_base_path, out_path) => {
+ const ui_path = path.relative(base_out_path,
+ path.join(script_base_path, 'app', 'ui.js'));
+ return writeFile(out_path, `SystemJS.import("${ui_path}");`)
+ .then(() => {
+ console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`);
+ // FIXME: Should probably be in the legacy directory
+ const promise_path = path.relative(base_out_path,
+ path.join(base_out_path, 'vendor', 'promise.js'));
+ const systemjs_path = path.relative(base_out_path,
+ path.join(script_base_path, 'system-production.js'));
+ return [ promise_path, systemjs_path ];
+ });
+ },
+ noCopyOverride: (paths, no_copy_files) => {
+ no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
+ },
+ },
+ 'umd': {
+ optionsOverride: (opts) => {
+ // umd supports properly shifting the default export to work as normal
+ opts.plugins.unshift("add-module-exports");
+ },
+ },
+};
diff --git a/systemvm/agent/noVNC/utils/validate b/systemvm/agent/noVNC/utils/validate
new file mode 100755
index 0000000..a6b5507
--- /dev/null
+++ b/systemvm/agent/noVNC/utils/validate
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -e
+
+RET=0
+
+OUT=`mktemp`
+
+for fn in "$@"; do
+ echo "Validating $fn..."
+ echo
+
+ case $fn in
+ *.html)
+ type="text/html"
+ ;;
+ *.css)
+ type="text/css"
+ ;;
+ *)
+ echo "Unknown format!"
+ echo
+ RET=1
+ continue
+ ;;
+ esac
+
+ curl --silent \
+ --header "Content-Type: ${type}; charset=utf-8" \
+ --data-binary @${fn} \
+ https://validator.w3.org/nu/?out=text > $OUT
+ cat $OUT
+ echo
+
+ # We don't fail the check for warnings as some warnings are
+ # not relevant for us, and we don't currently have a way to
+ # ignore just those
+ if grep -q -s -E "^Error:" $OUT; then
+ RET=1
+ fi
+done
+
+rm $OUT
+
+exit $RET
diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/README.md b/systemvm/agent/noVNC/vendor/browser-es-module-loader/README.md
new file mode 100644
index 0000000..c26867f
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/README.md
@@ -0,0 +1,15 @@
+Custom Browser ES Module Loader
+===============================
+
+This is a module loader using babel and the ES Module Loader polyfill.
+It's based heavily on
+https://github.com/ModuleLoader/browser-es-module-loader, but uses
+WebWorkers to compile the modules in the background.
+
+To generate, run `rollup -c` in this directory, and then run `browserify
+src/babel-worker.js > dist/babel-worker.js`.
+
+LICENSE
+-------
+
+MIT
diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/rollup.config.js b/systemvm/agent/noVNC/vendor/browser-es-module-loader/rollup.config.js
new file mode 100644
index 0000000..4bf4a5f
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/rollup.config.js
@@ -0,0 +1,16 @@
+import nodeResolve from 'rollup-plugin-node-resolve';
+
+export default {
+ entry: 'src/browser-es-module-loader.js',
+ dest: 'dist/browser-es-module-loader.js',
+ format: 'umd',
+ moduleName: 'BrowserESModuleLoader',
+ sourceMap: true,
+
+ plugins: [
+ nodeResolve(),
+ ],
+
+ // skip rollup warnings (specifically the eval warning)
+ onwarn: function() {}
+};
diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/babel-worker.js b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/babel-worker.js
new file mode 100644
index 0000000..007bd68
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/babel-worker.js
@@ -0,0 +1,25 @@
+/*import { transform as babelTransform } from 'babel-core';
+import babelTransformDynamicImport from 'babel-plugin-syntax-dynamic-import';
+import babelTransformES2015ModulesSystemJS from 'babel-plugin-transform-es2015-modules-systemjs';*/
+
+// sadly, due to how rollup works, we can't use es6 imports here
+var babelTransform = require('babel-core').transform;
+var babelTransformDynamicImport = require('babel-plugin-syntax-dynamic-import');
+var babelTransformES2015ModulesSystemJS = require('babel-plugin-transform-es2015-modules-systemjs');
+var babelPresetES2015 = require('babel-preset-es2015');
+
+self.onmessage = function (evt) {
+ // transform source with Babel
+ var output = babelTransform(evt.data.source, {
+ compact: false,
+ filename: evt.data.key + '!transpiled',
+ sourceFileName: evt.data.key,
+ moduleIds: false,
+ sourceMaps: 'inline',
+ babelrc: false,
+ plugins: [babelTransformDynamicImport, babelTransformES2015ModulesSystemJS],
+ presets: [babelPresetES2015],
+ });
+
+ self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
+};
diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/browser-es-module-loader.js b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/browser-es-module-loader.js
new file mode 100644
index 0000000..efae617
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/browser-es-module-loader.js
@@ -0,0 +1,280 @@
+import RegisterLoader from 'es-module-loader/core/register-loader.js';
+import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
+
+import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
+import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
+
+var loader;
+
+// <script type="module"> support
+var anonSources = {};
+if (typeof document != 'undefined' && document.getElementsByTagName) {
+ var handleError = function(err) {
+ // dispatch an error event so that we can display in errors in browsers
+ // that don't yet support unhandledrejection
+ if (window.onunhandledrejection === undefined) {
+ try {
+ var evt = new Event('error');
+ } catch (_eventError) {
+ var evt = document.createEvent('Event');
+ evt.initEvent('error', true, true);
+ }
+ evt.message = err.message;
+ if (err.fileName) {
+ evt.filename = err.fileName;
+ evt.lineno = err.lineNumber;
+ evt.colno = err.columnNumber;
+ } else if (err.sourceURL) {
+ evt.filename = err.sourceURL;
+ evt.lineno = err.line;
+ evt.colno = err.column;
+ }
+ evt.error = err;
+ window.dispatchEvent(evt);
+ }
+
+ // throw so it still shows up in the console
+ throw err;
+ }
+
+ var ready = function() {
+ document.removeEventListener('DOMContentLoaded', ready, false );
+
+ var anonCnt = 0;
+
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0; i < scripts.length; i++) {
+ var script = scripts[i];
+ if (script.type == 'module' && !script.loaded) {
+ script.loaded = true;
+ if (script.src) {
+ loader.import(script.src).catch(handleError);
+ }
+ // anonymous modules supported via a custom naming scheme and registry
+ else {
+ var uri = './<anon' + ++anonCnt + '>.js';
+ if (script.id !== ""){
+ uri = "./" + script.id;
+ }
+
+ var anonName = resolveIfNotPlain(uri, baseURI);
+ anonSources[anonName] = script.innerHTML;
+ loader.import(anonName).catch(handleError);
+ }
+ }
+ }
+ }
+
+ // simple DOM ready
+ if (document.readyState !== 'loading')
+ setTimeout(ready);
+ else
+ document.addEventListener('DOMContentLoaded', ready, false);
+}
+
+function BrowserESModuleLoader(baseKey) {
+ if (baseKey)
+ this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
+
+ RegisterLoader.call(this);
+
+ var loader = this;
+
+ // ensure System.register is available
+ global.System = global.System || {};
+ if (typeof global.System.register == 'function')
+ var prevRegister = global.System.register;
+ global.System.register = function() {
+ loader.register.apply(loader, arguments);
+ if (prevRegister)
+ prevRegister.apply(this, arguments);
+ };
+}
+BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
+
+// normalize is never given a relative name like "./x", that part is already handled
+BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
+ var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
+ if (!resolved)
+ throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
+
+ return resolved;
+};
+
+function xhrFetch(url, resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ var load = function(source) {
+ resolve(xhr.responseText);
+ }
+ var error = function() {
+ reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
+ }
+
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ // in Chrome on file:/// URLs, status is 0
+ if (xhr.status == 0) {
+ if (xhr.responseText) {
+ load();
+ }
+ else {
+ // when responseText is empty, wait for load or error event
+ // to inform if it is a 404 or empty file
+ xhr.addEventListener('error', error);
+ xhr.addEventListener('load', load);
+ }
+ }
+ else if (xhr.status === 200) {
+ load();
+ }
+ else {
+ error();
+ }
+ }
+ };
+ xhr.open("GET", url, true);
+ xhr.send(null);
+}
+
+var WorkerPool = function (script, size) {
+ var current = document.currentScript;
+ // IE doesn't support currentScript
+ if (!current) {
+ // Find an entry with out basename
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i].src.indexOf("browser-es-module-loader.js") !== -1) {
+ current = scripts[i];
+ break;
+ }
+ }
+ if (!current)
+ throw Error("Could not find own <script> element");
+ }
+ script = current.src.substr(0, current.src.lastIndexOf("/")) + "/" + script;
+ this._workers = new Array(size);
+ this._ind = 0;
+ this._size = size;
+ this._jobs = 0;
+ this.onmessage = undefined;
+ this._stopTimeout = undefined;
+ for (var i = 0; i < size; i++) {
+ var wrkr = new Worker(script);
+ wrkr._count = 0;
+ wrkr._ind = i;
+ wrkr.onmessage = this._onmessage.bind(this, wrkr);
+ wrkr.onerror = this._onerror.bind(this);
+ this._workers[i] = wrkr;
+ }
+
+ this._checkJobs();
+};
+WorkerPool.prototype = {
+ postMessage: function (msg) {
+ if (this._stopTimeout !== undefined) {
+ clearTimeout(this._stopTimeout);
+ this._stopTimeout = undefined;
+ }
+ var wrkr = this._workers[this._ind % this._size];
+ wrkr._count++;
+ this._jobs++;
+ wrkr.postMessage(msg);
+ this._ind++;
+ },
+
+ _onmessage: function (wrkr, evt) {
+ wrkr._count--;
+ this._jobs--;
+ this.onmessage(evt, wrkr);
+ this._checkJobs();
+ },
+
+ _onerror: function(err) {
+ try {
+ var evt = new Event('error');
+ } catch (_eventError) {
+ var evt = document.createEvent('Event');
+ evt.initEvent('error', true, true);
+ }
+ evt.message = err.message;
+ evt.filename = err.filename;
+ evt.lineno = err.lineno;
+ evt.colno = err.colno;
+ evt.error = err.error;
+ window.dispatchEvent(evt);
+ },
+
+ _checkJobs: function () {
+ if (this._jobs === 0 && this._stopTimeout === undefined) {
+ // wait for 2s of inactivity before stopping (that should be enough for local loading)
+ this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
+ }
+ },
+
+ _stop: function () {
+ this._workers.forEach(function(wrkr) {
+ wrkr.terminate();
+ });
+ }
+};
+
+var promiseMap = new Map();
+var babelWorker = new WorkerPool('babel-worker.js', 3);
+babelWorker.onmessage = function (evt) {
+ var promFuncs = promiseMap.get(evt.data.key);
+ promFuncs.resolve(evt.data);
+ promiseMap.delete(evt.data.key);
+};
+
+// instantiate just needs to run System.register
+// so we fetch the source, convert into the Babel System module format, then evaluate it
+BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
+ var loader = this;
+
+ // load as ES with Babel converting into System.register
+ return new Promise(function(resolve, reject) {
+ // anonymous module
+ if (anonSources[key]) {
+ resolve(anonSources[key])
+ anonSources[key] = undefined;
+ }
+ // otherwise we fetch
+ else {
+ xhrFetch(key, resolve, reject);
+ }
+ })
+ .then(function(source) {
+ // check our cache first
+ var cacheEntry = localStorage.getItem(key);
+ if (cacheEntry) {
+ cacheEntry = JSON.parse(cacheEntry);
+ // TODO: store a hash instead
+ if (cacheEntry.source === source) {
+ return Promise.resolve({key: key, code: cacheEntry.code, source: cacheEntry.source});
+ }
+ }
+ return new Promise(function (resolve, reject) {
+ promiseMap.set(key, {resolve: resolve, reject: reject});
+ babelWorker.postMessage({key: key, source: source});
+ });
+ }).then(function (data) {
+ // evaluate without require, exports and module variables
+ // we leave module in for now to allow module.require access
+ try {
+ var cacheEntry = JSON.stringify({source: data.source, code: data.code});
+ localStorage.setItem(key, cacheEntry);
+ } catch (e) {
+ if (window.console) {
+ window.console.warn('Unable to cache transpiled version of ' + key + ': ' + e);
+ }
+ }
+ (0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
+ processAnonRegister();
+ });
+};
+
+// create a default loader instance in the browser
+if (isBrowser)
+ loader = new BrowserESModuleLoader();
+
+export default BrowserESModuleLoader;
diff --git a/systemvm/agent/noVNC/vendor/pako/LICENSE b/systemvm/agent/noVNC/vendor/pako/LICENSE
new file mode 100644
index 0000000..d082ae3
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/LICENSE
@@ -0,0 +1,21 @@
+(The MIT License)
+
+Copyright (C) 2014-2016 by Vitaly Puzrin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/systemvm/agent/noVNC/vendor/pako/README.md b/systemvm/agent/noVNC/vendor/pako/README.md
new file mode 100644
index 0000000..755df64
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/README.md
@@ -0,0 +1,6 @@
+This is an ES6-modules-compatible version of
+https://github.com/nodeca/pako, based on pako version 1.0.3.
+
+It's more-or-less a direct translation of the original, with unused parts
+removed, and the dynamic support for non-typed arrays removed (since ES6
+modules don't work well with dynamic exports).
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/utils/common.js b/systemvm/agent/noVNC/vendor/pako/lib/utils/common.js
new file mode 100644
index 0000000..576fd59
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/utils/common.js
@@ -0,0 +1,45 @@
+// reduce buffer size, avoiding mem copy
+export function shrinkBuf (buf, size) {
+ if (buf.length === size) { return buf; }
+ if (buf.subarray) { return buf.subarray(0, size); }
+ buf.length = size;
+ return buf;
+};
+
+
+export function arraySet (dest, src, src_offs, len, dest_offs) {
+ if (src.subarray && dest.subarray) {
+ dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
+ return;
+ }
+ // Fallback to ordinary array
+ for (var i = 0; i < len; i++) {
+ dest[dest_offs + i] = src[src_offs + i];
+ }
+}
+
+// Join array of chunks to single array.
+export function flattenChunks (chunks) {
+ var i, l, len, pos, chunk, result;
+
+ // calculate data length
+ len = 0;
+ for (i = 0, l = chunks.length; i < l; i++) {
+ len += chunks[i].length;
+ }
+
+ // join chunks
+ result = new Uint8Array(len);
+ pos = 0;
+ for (i = 0, l = chunks.length; i < l; i++) {
+ chunk = chunks[i];
+ result.set(chunk, pos);
+ pos += chunk.length;
+ }
+
+ return result;
+}
+
+export var Buf8 = Uint8Array;
+export var Buf16 = Uint16Array;
+export var Buf32 = Int32Array;
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/adler32.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/adler32.js
new file mode 100644
index 0000000..058a534
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/adler32.js
@@ -0,0 +1,27 @@
+// Note: adler32 takes 12% for level 0 and 2% for level 6.
+// It doesn't worth to make additional optimizationa as in original.
+// Small size is preferable.
+
+export default function adler32(adler, buf, len, pos) {
+ var s1 = (adler & 0xffff) |0,
+ s2 = ((adler >>> 16) & 0xffff) |0,
+ n = 0;
+
+ while (len !== 0) {
+ // Set limit ~ twice less than 5552, to keep
+ // s2 in 31-bits, because we force signed ints.
+ // in other case %= will fail.
+ n = len > 2000 ? 2000 : len;
+ len -= n;
+
+ do {
+ s1 = (s1 + buf[pos++]) |0;
+ s2 = (s2 + s1) |0;
+ } while (--n);
+
+ s1 %= 65521;
+ s2 %= 65521;
+ }
+
+ return (s1 | (s2 << 16)) |0;
+}
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/constants.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/constants.js
new file mode 100644
index 0000000..7d80502
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/constants.js
@@ -0,0 +1,47 @@
+export default {
+
+ /* Allowed flush values; see deflate() and inflate() below for details */
+ Z_NO_FLUSH: 0,
+ Z_PARTIAL_FLUSH: 1,
+ Z_SYNC_FLUSH: 2,
+ Z_FULL_FLUSH: 3,
+ Z_FINISH: 4,
+ Z_BLOCK: 5,
+ Z_TREES: 6,
+
+ /* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+ Z_OK: 0,
+ Z_STREAM_END: 1,
+ Z_NEED_DICT: 2,
+ Z_ERRNO: -1,
+ Z_STREAM_ERROR: -2,
+ Z_DATA_ERROR: -3,
+ //Z_MEM_ERROR: -4,
+ Z_BUF_ERROR: -5,
+ //Z_VERSION_ERROR: -6,
+
+ /* compression levels */
+ Z_NO_COMPRESSION: 0,
+ Z_BEST_SPEED: 1,
+ Z_BEST_COMPRESSION: 9,
+ Z_DEFAULT_COMPRESSION: -1,
+
+
+ Z_FILTERED: 1,
+ Z_HUFFMAN_ONLY: 2,
+ Z_RLE: 3,
+ Z_FIXED: 4,
+ Z_DEFAULT_STRATEGY: 0,
+
+ /* Possible values of the data_type field (though see inflate()) */
+ Z_BINARY: 0,
+ Z_TEXT: 1,
+ //Z_ASCII: 1, // = Z_TEXT (deprecated)
+ Z_UNKNOWN: 2,
+
+ /* The deflate compression method */
+ Z_DEFLATED: 8
+ //Z_NULL: null // Use -1 or null inline, depending on var type
+};
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/crc32.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/crc32.js
new file mode 100644
index 0000000..611ffb29b
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/crc32.js
@@ -0,0 +1,36 @@
+// Note: we can't get significant speed boost here.
+// So write code to minimize size - no pregenerated tables
+// and array tools dependencies.
+
+
+// Use ordinary array, since untyped makes no boost here
+export default function makeTable() {
+ var c, table = [];
+
+ for (var n = 0; n < 256; n++) {
+ c = n;
+ for (var k = 0; k < 8; k++) {
+ c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+ }
+ table[n] = c;
+ }
+
+ return table;
+}
+
+// Create table on load. Just 255 signed longs. Not a problem.
+var crcTable = makeTable();
+
+
+function crc32(crc, buf, len, pos) {
+ var t = crcTable,
+ end = pos + len;
+
+ crc ^= -1;
+
+ for (var i = pos; i < end; i++) {
+ crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+ }
+
+ return (crc ^ (-1)); // >>> 0;
+}
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/deflate.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/deflate.js
new file mode 100644
index 0000000..c51915e
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/deflate.js
@@ -0,0 +1,1846 @@
+import * as utils from "../utils/common.js";
+import * as trees from "./trees.js";
+import adler32 from "./adler32.js";
+import crc32 from "./crc32.js";
+import msg from "./messages.js";
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+/* Allowed flush values; see deflate() and inflate() below for details */
+var Z_NO_FLUSH = 0;
+var Z_PARTIAL_FLUSH = 1;
+//var Z_SYNC_FLUSH = 2;
+var Z_FULL_FLUSH = 3;
+var Z_FINISH = 4;
+var Z_BLOCK = 5;
+//var Z_TREES = 6;
+
+
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+var Z_OK = 0;
+var Z_STREAM_END = 1;
+//var Z_NEED_DICT = 2;
+//var Z_ERRNO = -1;
+var Z_STREAM_ERROR = -2;
+var Z_DATA_ERROR = -3;
+//var Z_MEM_ERROR = -4;
+var Z_BUF_ERROR = -5;
+//var Z_VERSION_ERROR = -6;
+
+
+/* compression levels */
+//var Z_NO_COMPRESSION = 0;
+//var Z_BEST_SPEED = 1;
+//var Z_BEST_COMPRESSION = 9;
+var Z_DEFAULT_COMPRESSION = -1;
+
+
+var Z_FILTERED = 1;
+var Z_HUFFMAN_ONLY = 2;
+var Z_RLE = 3;
+var Z_FIXED = 4;
+var Z_DEFAULT_STRATEGY = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+//var Z_BINARY = 0;
+//var Z_TEXT = 1;
+//var Z_ASCII = 1; // = Z_TEXT
+var Z_UNKNOWN = 2;
+
+
+/* The deflate compression method */
+var Z_DEFLATED = 8;
+
+/*============================================================================*/
+
+
+var MAX_MEM_LEVEL = 9;
+/* Maximum value for memLevel in deflateInit2 */
+var MAX_WBITS = 15;
+/* 32K LZ77 window */
+var DEF_MEM_LEVEL = 8;
+
+
+var LENGTH_CODES = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+var LITERALS = 256;
+/* number of literal bytes 0..255 */
+var L_CODES = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+var D_CODES = 30;
+/* number of distance codes */
+var BL_CODES = 19;
+/* number of codes used to transfer the bit lengths */
+var HEAP_SIZE = 2 * L_CODES + 1;
+/* maximum heap size */
+var MAX_BITS = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+var MIN_MATCH = 3;
+var MAX_MATCH = 258;
+var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);
+
+var PRESET_DICT = 0x20;
+
+var INIT_STATE = 42;
+var EXTRA_STATE = 69;
+var NAME_STATE = 73;
+var COMMENT_STATE = 91;
+var HCRC_STATE = 103;
+var BUSY_STATE = 113;
+var FINISH_STATE = 666;
+
+var BS_NEED_MORE = 1; /* block not completed, need more input or more output */
+var BS_BLOCK_DONE = 2; /* block flush performed */
+var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
+var BS_FINISH_DONE = 4; /* finish done, accept no more input or output */
+
+var OS_CODE = 0x03; // Unix :) . Don't detect, use this default.
+
+function err(strm, errorCode) {
+ strm.msg = msg[errorCode];
+ return errorCode;
+}
+
+function rank(f) {
+ return ((f) << 1) - ((f) > 4 ? 9 : 0);
+}
+
+function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output goes
+ * through this function so some applications may wish to modify it
+ * to avoid allocating a large strm->output buffer and copying into it.
+ * (See also read_buf()).
+ */
+function flush_pending(strm) {
+ var s = strm.state;
+
+ //_tr_flush_bits(s);
+ var len = s.pending;
+ if (len > strm.avail_out) {
+ len = strm.avail_out;
+ }
+ if (len === 0) { return; }
+
+ utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);
+ strm.next_out += len;
+ s.pending_out += len;
+ strm.total_out += len;
+ strm.avail_out -= len;
+ s.pending -= len;
+ if (s.pending === 0) {
+ s.pending_out = 0;
+ }
+}
+
+
+function flush_block_only(s, last) {
+ trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
+ s.block_start = s.strstart;
+ flush_pending(s.strm);
+}
+
+
+function put_byte(s, b) {
+ s.pending_buf[s.pending++] = b;
+}
+
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+function putShortMSB(s, b) {
+// put_byte(s, (Byte)(b >> 8));
+// put_byte(s, (Byte)(b & 0xff));
+ s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
+ s.pending_buf[s.pending++] = b & 0xff;
+}
+
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read. All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->input buffer and copying from it.
+ * (See also flush_pending()).
+ */
+function read_buf(strm, buf, start, size) {
+ var len = strm.avail_in;
+
+ if (len > size) { len = size; }
+ if (len === 0) { return 0; }
+
+ strm.avail_in -= len;
+
+ // zmemcpy(buf, strm->next_in, len);
+ utils.arraySet(buf, strm.input, strm.next_in, len, start);
+ if (strm.state.wrap === 1) {
+ strm.adler = adler32(strm.adler, buf, len, start);
+ }
+
+ else if (strm.state.wrap === 2) {
+ strm.adler = crc32(strm.adler, buf, len, start);
+ }
+
+ strm.next_in += len;
+ strm.total_in += len;
+
+ return len;
+}
+
+
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+function longest_match(s, cur_match) {
+ var chain_length = s.max_chain_length; /* max hash chain length */
+ var scan = s.strstart; /* current string */
+ var match; /* matched string */
+ var len; /* length of current match */
+ var best_len = s.prev_length; /* best match length so far */
+ var nice_match = s.nice_match; /* stop if match long enough */
+ var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
+ s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;
+
+ var _win = s.window; // shortcut
+
+ var wmask = s.w_mask;
+ var prev = s.prev;
+
+ /* Stop when cur_match becomes <= limit. To simplify the code,
+ * we prevent matches with the string of window index 0.
+ */
+
+ var strend = s.strstart + MAX_MATCH;
+ var scan_end1 = _win[scan + best_len - 1];
+ var scan_end = _win[scan + best_len];
+
+ /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+ * It is easy to get rid of this optimization if necessary.
+ */
+ // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+ /* Do not waste too much time if we already have a good match: */
+ if (s.prev_length >= s.good_match) {
+ chain_length >>= 2;
+ }
+ /* Do not look for matches beyond the end of the input. This is necessary
+ * to make deflate deterministic.
+ */
+ if (nice_match > s.lookahead) { nice_match = s.lookahead; }
+
+ // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+ do {
+ // Assert(cur_match < s->strstart, "no future");
+ match = cur_match;
+
+ /* Skip to next match if the match length cannot increase
+ * or if the match length is less than 2. Note that the checks below
+ * for insufficient lookahead only occur occasionally for performance
+ * reasons. Therefore uninitialized memory will be accessed, and
+ * conditional jumps will be made that depend on those values.
+ * However the length of the match is limited to the lookahead, so
+ * the output of deflate is not affected by the uninitialized values.
+ */
+
+ if (_win[match + best_len] !== scan_end ||
+ _win[match + best_len - 1] !== scan_end1 ||
+ _win[match] !== _win[scan] ||
+ _win[++match] !== _win[scan + 1]) {
+ continue;
+ }
+
+ /* The check at best_len-1 can be removed because it will be made
+ * again later. (This heuristic is not always a win.)
+ * It is not necessary to compare scan[2] and match[2] since they
+ * are always equal when the other bytes match, given that
+ * the hash keys are equal and that HASH_BITS >= 8.
+ */
+ scan += 2;
+ match++;
+ // Assert(*scan == *match, "match[2]?");
+
+ /* We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart+258.
+ */
+ do {
+ // Do nothing
+ } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ scan < strend);
+
+ // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+ len = MAX_MATCH - (strend - scan);
+ scan = strend - MAX_MATCH;
+
+ if (len > best_len) {
+ s.match_start = cur_match;
+ best_len = len;
+ if (len >= nice_match) {
+ break;
+ }
+ scan_end1 = _win[scan + best_len - 1];
+ scan_end = _win[scan + best_len];
+ }
+ } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);
+
+ if (best_len <= s.lookahead) {
+ return best_len;
+ }
+ return s.lookahead;
+}
+
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ * At least one byte has been read, or avail_in == 0; reads are
+ * performed for at least two bytes (required for the zip translate_eol
+ * option -- not supported here).
+ */
+function fill_window(s) {
+ var _w_size = s.w_size;
+ var p, n, m, more, str;
+
+ //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+ do {
+ more = s.window_size - s.lookahead - s.strstart;
+
+ // JS ints have 32 bit, block below not needed
+ /* Deal with !@#$% 64K limit: */
+ //if (sizeof(int) <= 2) {
+ // if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+ // more = wsize;
+ //
+ // } else if (more == (unsigned)(-1)) {
+ // /* Very unlikely, but possible on 16 bit machine if
+ // * strstart == 0 && lookahead == 1 (input done a byte at time)
+ // */
+ // more--;
+ // }
+ //}
+
+
+ /* If the window is almost full and there is insufficient lookahead,
+ * move the upper half to the lower one to make room in the upper half.
+ */
+ if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {
+
+ utils.arraySet(s.window, s.window, _w_size, _w_size, 0);
+ s.match_start -= _w_size;
+ s.strstart -= _w_size;
+ /* we now have strstart >= MAX_DIST */
+ s.block_start -= _w_size;
+
+ /* Slide the hash table (could be avoided with 32 bit values
+ at the expense of memory usage). We slide even when level == 0
+ to keep the hash table consistent if we switch back to level > 0
+ later. (Using level 0 permanently is not an optimal usage of
+ zlib, so we don't care about this pathological case.)
+ */
+
+ n = s.hash_size;
+ p = n;
+ do {
+ m = s.head[--p];
+ s.head[p] = (m >= _w_size ? m - _w_size : 0);
+ } while (--n);
+
+ n = _w_size;
+ p = n;
+ do {
+ m = s.prev[--p];
+ s.prev[p] = (m >= _w_size ? m - _w_size : 0);
+ /* If n is not on any hash chain, prev[n] is garbage but
+ * its value will never be used.
+ */
+ } while (--n);
+
+ more += _w_size;
+ }
+ if (s.strm.avail_in === 0) {
+ break;
+ }
+
+ /* If there was no sliding:
+ * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+ * more == window_size - lookahead - strstart
+ * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+ * => more >= window_size - 2*WSIZE + 2
+ * In the BIG_MEM or MMAP case (not yet supported),
+ * window_size == input_size + MIN_LOOKAHEAD &&
+ * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+ * Otherwise, window_size == 2*WSIZE so more >= 2.
+ * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+ */
+ //Assert(more >= 2, "more < 2");
+ n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
+ s.lookahead += n;
+
+ /* Initialize the hash value now that we have some input: */
+ if (s.lookahead + s.insert >= MIN_MATCH) {
+ str = s.strstart - s.insert;
+ s.ins_h = s.window[str];
+
+ /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask;
+//#if MIN_MATCH != 3
+// Call update_hash() MIN_MATCH-3 more times
+//#endif
+ while (s.insert) {
+ /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
+
+ s.prev[str & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = str;
+ str++;
+ s.insert--;
+ if (s.lookahead + s.insert < MIN_MATCH) {
+ break;
+ }
+ }
+ }
+ /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+ * but this is not important since only literal bytes will be emitted.
+ */
+
+ } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);
+
+ /* If the WIN_INIT bytes after the end of the current data have never been
+ * written, then zero those bytes in order to avoid memory check reports of
+ * the use of uninitialized (or uninitialised as Julian writes) bytes by
+ * the longest match routines. Update the high water mark for the next
+ * time through here. WIN_INIT is set to MAX_MATCH since the longest match
+ * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+ */
+// if (s.high_water < s.window_size) {
+// var curr = s.strstart + s.lookahead;
+// var init = 0;
+//
+// if (s.high_water < curr) {
+// /* Previous high water mark below current data -- zero WIN_INIT
+// * bytes or up to end of window, whichever is less.
+// */
+// init = s.window_size - curr;
+// if (init > WIN_INIT)
+// init = WIN_INIT;
+// zmemzero(s->window + curr, (unsigned)init);
+// s->high_water = curr + init;
+// }
+// else if (s->high_water < (ulg)curr + WIN_INIT) {
+// /* High water mark at or above current data, but below current data
+// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+// * to end of window, whichever is less.
+// */
+// init = (ulg)curr + WIN_INIT - s->high_water;
+// if (init > s->window_size - s->high_water)
+// init = s->window_size - s->high_water;
+// zmemzero(s->window + s->high_water, (unsigned)init);
+// s->high_water += init;
+// }
+// }
+//
+// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+// "not enough room for search");
+}
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ * This function does not insert new strings in the dictionary since
+ * uncompressible data is probably not useful. This function is used
+ * only for the level=0 compression option.
+ * NOTE: this function should be optimized to avoid extra copying from
+ * window to pending_buf.
+ */
+function deflate_stored(s, flush) {
+ /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
+ * to pending_buf_size, and each stored block has a 5 byte header:
+ */
+ var max_block_size = 0xffff;
+
+ if (max_block_size > s.pending_buf_size - 5) {
+ max_block_size = s.pending_buf_size - 5;
+ }
+
+ /* Copy as much as possible from input to output: */
+ for (;;) {
+ /* Fill the window as much as possible: */
+ if (s.lookahead <= 1) {
+
+ //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
+ // s->block_start >= (long)s->w_size, "slide too late");
+// if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
+// s.block_start >= s.w_size)) {
+// throw new Error("slide too late");
+// }
+
+ fill_window(s);
+ if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+
+ if (s.lookahead === 0) {
+ break;
+ }
+ /* flush the current block */
+ }
+ //Assert(s->block_start >= 0L, "block gone");
+// if (s.block_start < 0) throw new Error("block gone");
+
+ s.strstart += s.lookahead;
+ s.lookahead = 0;
+
+ /* Emit a stored block if pending_buf will be full: */
+ var max_start = s.block_start + max_block_size;
+
+ if (s.strstart === 0 || s.strstart >= max_start) {
+ /* strstart == 0 is possible when wraparound on 16-bit machine */
+ s.lookahead = s.strstart - max_start;
+ s.strstart = max_start;
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+
+
+ }
+ /* Flush if we may have to slide, otherwise block_start may become
+ * negative and the data will be gone:
+ */
+ if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+
+ s.insert = 0;
+
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+
+ if (s.strstart > s.block_start) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+
+ return BS_NEED_MORE;
+}
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+function deflate_fast(s, flush) {
+ var hash_head; /* head of the hash chain */
+ var bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s.lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ if (s.lookahead === 0) {
+ break; /* flush the current block */
+ }
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = 0/*NIL*/;
+ if (s.lookahead >= MIN_MATCH) {
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ * At this point we have always match_length < MIN_MATCH
+ */
+ if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s.match_length = longest_match(s, hash_head);
+ /* longest_match() sets match_start */
+ }
+ if (s.match_length >= MIN_MATCH) {
+ // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only
+
+ /*** _tr_tally_dist(s, s.strstart - s.match_start,
+ s.match_length - MIN_MATCH, bflush); ***/
+ bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);
+
+ s.lookahead -= s.match_length;
+
+ /* Insert new strings in the hash table only if the match length
+ * is not too large. This saves time but degrades compression.
+ */
+ if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {
+ s.match_length--; /* string at strstart already in table */
+ do {
+ s.strstart++;
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+ * always MIN_MATCH bytes ahead.
+ */
+ } while (--s.match_length !== 0);
+ s.strstart++;
+ } else
+ {
+ s.strstart += s.match_length;
+ s.match_length = 0;
+ s.ins_h = s.window[s.strstart];
+ /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask;
+
+//#if MIN_MATCH != 3
+// Call UPDATE_HASH() MIN_MATCH-3 more times
+//#endif
+ /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+ * matter since it will be recomputed at next deflate call.
+ */
+ }
+ } else {
+ /* No match, output a literal byte */
+ //Tracevv((stderr,"%c", s.window[s.strstart]));
+ /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+ bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+
+ s.lookahead--;
+ s.strstart++;
+ }
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+ s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ return BS_BLOCK_DONE;
+}
+
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+function deflate_slow(s, flush) {
+ var hash_head; /* head of hash chain */
+ var bflush; /* set if current block must be flushed */
+
+ var max_insert;
+
+ /* Process the input block. */
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s.lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ if (s.lookahead === 0) { break; } /* flush the current block */
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = 0/*NIL*/;
+ if (s.lookahead >= MIN_MATCH) {
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ */
+ s.prev_length = s.match_length;
+ s.prev_match = s.match_start;
+ s.match_length = MIN_MATCH - 1;
+
+ if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
+ s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s.match_length = longest_match(s, hash_head);
+ /* longest_match() sets match_start */
+
+ if (s.match_length <= 5 &&
+ (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {
+
+ /* If prev_match is also MIN_MATCH, match_start is garbage
+ * but we will ignore the current match anyway.
+ */
+ s.match_length = MIN_MATCH - 1;
+ }
+ }
+ /* If there was a match at the previous step and the current
+ * match is not better, output the previous match:
+ */
+ if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
+ max_insert = s.strstart + s.lookahead - MIN_MATCH;
+ /* Do not insert strings in hash table beyond this. */
+
+ //check_match(s, s.strstart-1, s.prev_match, s.prev_length);
+
+ /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
+ s.prev_length - MIN_MATCH, bflush);***/
+ bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
+ /* Insert in hash table all strings up to the end of the match.
+ * strstart-1 and strstart are already inserted. If there is not
+ * enough lookahead, the last two strings are not inserted in
+ * the hash table.
+ */
+ s.lookahead -= s.prev_length - 1;
+ s.prev_length -= 2;
+ do {
+ if (++s.strstart <= max_insert) {
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ }
+ } while (--s.prev_length !== 0);
+ s.match_available = 0;
+ s.match_length = MIN_MATCH - 1;
+ s.strstart++;
+
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+
+ } else if (s.match_available) {
+ /* If there was no match at the previous position, output a
+ * single literal. If there was a match but the current match
+ * is longer, truncate the previous match to a single literal.
+ */
+ //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+ bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
+
+ if (bflush) {
+ /*** FLUSH_BLOCK_ONLY(s, 0) ***/
+ flush_block_only(s, false);
+ /***/
+ }
+ s.strstart++;
+ s.lookahead--;
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ } else {
+ /* There is no previous match to compare with, wait for
+ * the next step to decide.
+ */
+ s.match_available = 1;
+ s.strstart++;
+ s.lookahead--;
+ }
+ }
+ //Assert (flush != Z_NO_FLUSH, "no flush?");
+ if (s.match_available) {
+ //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+ bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
+
+ s.match_available = 0;
+ }
+ s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+
+ return BS_BLOCK_DONE;
+}
+
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one. Do not maintain a hash table. (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+function deflate_rle(s, flush) {
+ var bflush; /* set if current block must be flushed */
+ var prev; /* byte at distance one to match */
+ var scan, strend; /* scan goes up to strend for length of run */
+
+ var _win = s.window;
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the longest run, plus one for the unrolled loop.
+ */
+ if (s.lookahead <= MAX_MATCH) {
+ fill_window(s);
+ if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ if (s.lookahead === 0) { break; } /* flush the current block */
+ }
+
+ /* See how many times the previous byte repeats */
+ s.match_length = 0;
+ if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
+ scan = s.strstart - 1;
+ prev = _win[scan];
+ if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
+ strend = s.strstart + MAX_MATCH;
+ do {
+ // Do nothing
+ } while (prev === _win[++scan] && prev === _win[++scan] &&
+ prev === _win[++scan] && prev === _win[++scan] &&
+ prev === _win[++scan] && prev === _win[++scan] &&
+ prev === _win[++scan] && prev === _win[++scan] &&
+ scan < strend);
+ s.match_length = MAX_MATCH - (strend - scan);
+ if (s.match_length > s.lookahead) {
+ s.match_length = s.lookahead;
+ }
+ }
+ //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+ }
+
+ /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+ if (s.match_length >= MIN_MATCH) {
+ //check_match(s, s.strstart, s.strstart - 1, s.match_length);
+
+ /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
+ bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);
+
+ s.lookahead -= s.match_length;
+ s.strstart += s.match_length;
+ s.match_length = 0;
+ } else {
+ /* No match, output a literal byte */
+ //Tracevv((stderr,"%c", s->window[s->strstart]));
+ /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+ bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+
+ s.lookahead--;
+ s.strstart++;
+ }
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+ s.insert = 0;
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ return BS_BLOCK_DONE;
+}
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+function deflate_huff(s, flush) {
+ var bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we have a literal to write. */
+ if (s.lookahead === 0) {
+ fill_window(s);
+ if (s.lookahead === 0) {
+ if (flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ break; /* flush the current block */
+ }
+ }
+
+ /* Output a literal byte */
+ s.match_length = 0;
+ //Tracevv((stderr,"%c", s->window[s->strstart]));
+ /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+ bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+ s.lookahead--;
+ s.strstart++;
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+ s.insert = 0;
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ return BS_BLOCK_DONE;
+}
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+function Config(good_length, max_lazy, nice_length, max_chain, func) {
+ this.good_length = good_length;
+ this.max_lazy = max_lazy;
+ this.nice_length = nice_length;
+ this.max_chain = max_chain;
+ this.func = func;
+}
+
+var configuration_table;
+
+configuration_table = [
+ /* good lazy nice chain */
+ new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */
+ new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */
+ new Config(4, 5, 16, 8, deflate_fast), /* 2 */
+ new Config(4, 6, 32, 32, deflate_fast), /* 3 */
+
+ new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */
+ new Config(8, 16, 32, 32, deflate_slow), /* 5 */
+ new Config(8, 16, 128, 128, deflate_slow), /* 6 */
+ new Config(8, 32, 128, 256, deflate_slow), /* 7 */
+ new Config(32, 128, 258, 1024, deflate_slow), /* 8 */
+ new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */
+];
+
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+function lm_init(s) {
+ s.window_size = 2 * s.w_size;
+
+ /*** CLEAR_HASH(s); ***/
+ zero(s.head); // Fill with NIL (= 0);
+
+ /* Set the default configuration parameters:
+ */
+ s.max_lazy_match = configuration_table[s.level].max_lazy;
+ s.good_match = configuration_table[s.level].good_length;
+ s.nice_match = configuration_table[s.level].nice_length;
+ s.max_chain_length = configuration_table[s.level].max_chain;
+
+ s.strstart = 0;
+ s.block_start = 0;
+ s.lookahead = 0;
+ s.insert = 0;
+ s.match_length = s.prev_length = MIN_MATCH - 1;
+ s.match_available = 0;
+ s.ins_h = 0;
+}
+
+
+function DeflateState() {
+ this.strm = null; /* pointer back to this zlib stream */
+ this.status = 0; /* as the name implies */
+ this.pending_buf = null; /* output still pending */
+ this.pending_buf_size = 0; /* size of pending_buf */
+ this.pending_out = 0; /* next pending byte to output to the stream */
+ this.pending = 0; /* nb of bytes in the pending buffer */
+ this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */
+ this.gzhead = null; /* gzip header information to write */
+ this.gzindex = 0; /* where in extra, name, or comment */
+ this.method = Z_DEFLATED; /* can only be DEFLATED */
+ this.last_flush = -1; /* value of flush param for previous deflate call */
+
+ this.w_size = 0; /* LZ77 window size (32K by default) */
+ this.w_bits = 0; /* log2(w_size) (8..16) */
+ this.w_mask = 0; /* w_size - 1 */
+
+ this.window = null;
+ /* Sliding window. Input bytes are read into the second half of the window,
+ * and move to the first half later to keep a dictionary of at least wSize
+ * bytes. With this organization, matches are limited to a distance of
+ * wSize-MAX_MATCH bytes, but this ensures that IO is always
+ * performed with a length multiple of the block size.
+ */
+
+ this.window_size = 0;
+ /* Actual size of window: 2*wSize, except when the user input buffer
+ * is directly used as sliding window.
+ */
+
+ this.prev = null;
+ /* Link to older string with same hash index. To limit the size of this
+ * array to 64K, this link is maintained only for the last 32K strings.
+ * An index in this array is thus a window index modulo 32K.
+ */
+
+ this.head = null; /* Heads of the hash chains or NIL. */
+
+ this.ins_h = 0; /* hash index of string to be inserted */
+ this.hash_size = 0; /* number of elements in hash table */
+ this.hash_bits = 0; /* log2(hash_size) */
+ this.hash_mask = 0; /* hash_size-1 */
+
+ this.hash_shift = 0;
+ /* Number of bits by which ins_h must be shifted at each input
+ * step. It must be such that after MIN_MATCH steps, the oldest
+ * byte no longer takes part in the hash key, that is:
+ * hash_shift * MIN_MATCH >= hash_bits
+ */
+
+ this.block_start = 0;
+ /* Window position at the beginning of the current output block. Gets
+ * negative when the window is moved backwards.
+ */
+
+ this.match_length = 0; /* length of best match */
+ this.prev_match = 0; /* previous match */
+ this.match_available = 0; /* set if previous match exists */
+ this.strstart = 0; /* start of string to insert */
+ this.match_start = 0; /* start of matching string */
+ this.lookahead = 0; /* number of valid bytes ahead in window */
+
+ this.prev_length = 0;
+ /* Length of the best match at previous step. Matches not greater than this
+ * are discarded. This is used in the lazy match evaluation.
+ */
+
+ this.max_chain_length = 0;
+ /* To speed up deflation, hash chains are never searched beyond this
+ * length. A higher limit improves compression ratio but degrades the
+ * speed.
+ */
+
+ this.max_lazy_match = 0;
+ /* Attempt to find a better match only when the current match is strictly
+ * smaller than this value. This mechanism is used only for compression
+ * levels >= 4.
+ */
+ // That's alias to max_lazy_match, don't use directly
+ //this.max_insert_length = 0;
+ /* Insert new strings in the hash table only if the match length is not
+ * greater than this length. This saves time but degrades compression.
+ * max_insert_length is used only for compression levels <= 3.
+ */
+
+ this.level = 0; /* compression level (1..9) */
+ this.strategy = 0; /* favor or force Huffman coding*/
+
+ this.good_match = 0;
+ /* Use a faster search when the previous match is longer than this */
+
+ this.nice_match = 0; /* Stop searching when current match exceeds this */
+
+ /* used by trees.c: */
+
+ /* Didn't use ct_data typedef below to suppress compiler warning */
+
+ // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */
+ // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+ // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */
+
+ // Use flat array of DOUBLE size, with interleaved fata,
+ // because JS does not support effective
+ this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2);
+ this.dyn_dtree = new utils.Buf16((2 * D_CODES + 1) * 2);
+ this.bl_tree = new utils.Buf16((2 * BL_CODES + 1) * 2);
+ zero(this.dyn_ltree);
+ zero(this.dyn_dtree);
+ zero(this.bl_tree);
+
+ this.l_desc = null; /* desc. for literal tree */
+ this.d_desc = null; /* desc. for distance tree */
+ this.bl_desc = null; /* desc. for bit length tree */
+
+ //ush bl_count[MAX_BITS+1];
+ this.bl_count = new utils.Buf16(MAX_BITS + 1);
+ /* number of codes at each bit length for an optimal tree */
+
+ //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */
+ this.heap = new utils.Buf16(2 * L_CODES + 1); /* heap used to build the Huffman trees */
+ zero(this.heap);
+
+ this.heap_len = 0; /* number of elements in the heap */
+ this.heap_max = 0; /* element of largest frequency */
+ /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+ * The same heap array is used to build all trees.
+ */
+
+ this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1];
+ zero(this.depth);
+ /* Depth of each subtree used as tie breaker for trees of equal frequency
+ */
+
+ this.l_buf = 0; /* buffer index for literals or lengths */
+
+ this.lit_bufsize = 0;
+ /* Size of match buffer for literals/lengths. There are 4 reasons for
+ * limiting lit_bufsize to 64K:
+ * - frequencies can be kept in 16 bit counters
+ * - if compression is not successful for the first block, all input
+ * data is still in the window so we can still emit a stored block even
+ * when input comes from standard input. (This can also be done for
+ * all blocks if lit_bufsize is not greater than 32K.)
+ * - if compression is not successful for a file smaller than 64K, we can
+ * even emit a stored file instead of a stored block (saving 5 bytes).
+ * This is applicable only for zip (not gzip or zlib).
+ * - creating new Huffman trees less frequently may not provide fast
+ * adaptation to changes in the input data statistics. (Take for
+ * example a binary file with poorly compressible code followed by
+ * a highly compressible string table.) Smaller buffer sizes give
+ * fast adaptation but have of course the overhead of transmitting
+ * trees more frequently.
+ * - I can't count above 4
+ */
+
+ this.last_lit = 0; /* running index in l_buf */
+
+ this.d_buf = 0;
+ /* Buffer index for distances. To simplify the code, d_buf and l_buf have
+ * the same number of elements. To use different lengths, an extra flag
+ * array would be necessary.
+ */
+
+ this.opt_len = 0; /* bit length of current block with optimal trees */
+ this.static_len = 0; /* bit length of current block with static trees */
+ this.matches = 0; /* number of string matches in current block */
+ this.insert = 0; /* bytes at end of window left to insert */
+
+
+ this.bi_buf = 0;
+ /* Output buffer. bits are inserted starting at the bottom (least
+ * significant bits).
+ */
+ this.bi_valid = 0;
+ /* Number of valid bits in bi_buf. All bits above the last valid bit
+ * are always zero.
+ */
+
+ // Used for window memory init. We safely ignore it for JS. That makes
+ // sense only for pointers and memory check tools.
+ //this.high_water = 0;
+ /* High water mark offset in window for initialized bytes -- bytes above
+ * this are set to zero in order to avoid memory check warnings when
+ * longest match routines access bytes past the input. This is then
+ * updated to the new high water mark.
+ */
+}
+
+
+function deflateResetKeep(strm) {
+ var s;
+
+ if (!strm || !strm.state) {
+ return err(strm, Z_STREAM_ERROR);
+ }
+
+ strm.total_in = strm.total_out = 0;
+ strm.data_type = Z_UNKNOWN;
+
+ s = strm.state;
+ s.pending = 0;
+ s.pending_out = 0;
+
+ if (s.wrap < 0) {
+ s.wrap = -s.wrap;
+ /* was made negative by deflate(..., Z_FINISH); */
+ }
+ s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
+ strm.adler = (s.wrap === 2) ?
+ 0 // crc32(0, Z_NULL, 0)
+ :
+ 1; // adler32(0, Z_NULL, 0)
+ s.last_flush = Z_NO_FLUSH;
+ trees._tr_init(s);
+ return Z_OK;
+}
+
+
+function deflateReset(strm) {
+ var ret = deflateResetKeep(strm);
+ if (ret === Z_OK) {
+ lm_init(strm.state);
+ }
+ return ret;
+}
+
+
+function deflateSetHeader(strm, head) {
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
+ strm.state.gzhead = head;
+ return Z_OK;
+}
+
+
+function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
+ if (!strm) { // === Z_NULL
+ return Z_STREAM_ERROR;
+ }
+ var wrap = 1;
+
+ if (level === Z_DEFAULT_COMPRESSION) {
+ level = 6;
+ }
+
+ if (windowBits < 0) { /* suppress zlib wrapper */
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+
+ else if (windowBits > 15) {
+ wrap = 2; /* write gzip wrapper instead */
+ windowBits -= 16;
+ }
+
+
+ if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
+ windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+ strategy < 0 || strategy > Z_FIXED) {
+ return err(strm, Z_STREAM_ERROR);
+ }
+
+
+ if (windowBits === 8) {
+ windowBits = 9;
+ }
+ /* until 256-byte window bug fixed */
+
+ var s = new DeflateState();
+
+ strm.state = s;
+ s.strm = strm;
+
+ s.wrap = wrap;
+ s.gzhead = null;
+ s.w_bits = windowBits;
+ s.w_size = 1 << s.w_bits;
+ s.w_mask = s.w_size - 1;
+
+ s.hash_bits = memLevel + 7;
+ s.hash_size = 1 << s.hash_bits;
+ s.hash_mask = s.hash_size - 1;
+ s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);
+
+ s.window = new utils.Buf8(s.w_size * 2);
+ s.head = new utils.Buf16(s.hash_size);
+ s.prev = new utils.Buf16(s.w_size);
+
+ // Don't need mem init magic for JS.
+ //s.high_water = 0; /* nothing written to s->window yet */
+
+ s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+ s.pending_buf_size = s.lit_bufsize * 4;
+
+ //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+ //s->pending_buf = (uchf *) overlay;
+ s.pending_buf = new utils.Buf8(s.pending_buf_size);
+
+ // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
+ //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+ s.d_buf = 1 * s.lit_bufsize;
+
+ //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+ s.l_buf = (1 + 2) * s.lit_bufsize;
+
+ s.level = level;
+ s.strategy = strategy;
+ s.method = method;
+
+ return deflateReset(strm);
+}
+
+function deflateInit(strm, level) {
+ return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+}
+
+
+function deflate(strm, flush) {
+ var old_flush, s;
+ var beg, val; // for gzip header write only
+
+ if (!strm || !strm.state ||
+ flush > Z_BLOCK || flush < 0) {
+ return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
+ }
+
+ s = strm.state;
+
+ if (!strm.output ||
+ (!strm.input && strm.avail_in !== 0) ||
+ (s.status === FINISH_STATE && flush !== Z_FINISH)) {
+ return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
+ }
+
+ s.strm = strm; /* just in case */
+ old_flush = s.last_flush;
+ s.last_flush = flush;
+
+ /* Write the header */
+ if (s.status === INIT_STATE) {
+
+ if (s.wrap === 2) { // GZIP header
+ strm.adler = 0; //crc32(0L, Z_NULL, 0);
+ put_byte(s, 31);
+ put_byte(s, 139);
+ put_byte(s, 8);
+ if (!s.gzhead) { // s->gzhead == Z_NULL
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, s.level === 9 ? 2 :
+ (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+ 4 : 0));
+ put_byte(s, OS_CODE);
+ s.status = BUSY_STATE;
+ }
+ else {
+ put_byte(s, (s.gzhead.text ? 1 : 0) +
+ (s.gzhead.hcrc ? 2 : 0) +
+ (!s.gzhead.extra ? 0 : 4) +
+ (!s.gzhead.name ? 0 : 8) +
+ (!s.gzhead.comment ? 0 : 16)
+ );
+ put_byte(s, s.gzhead.time & 0xff);
+ put_byte(s, (s.gzhead.time >> 8) & 0xff);
+ put_byte(s, (s.gzhead.time >> 16) & 0xff);
+ put_byte(s, (s.gzhead.time >> 24) & 0xff);
+ put_byte(s, s.level === 9 ? 2 :
+ (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+ 4 : 0));
+ put_byte(s, s.gzhead.os & 0xff);
+ if (s.gzhead.extra && s.gzhead.extra.length) {
+ put_byte(s, s.gzhead.extra.length & 0xff);
+ put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
+ }
+ if (s.gzhead.hcrc) {
+ strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);
+ }
+ s.gzindex = 0;
+ s.status = EXTRA_STATE;
+ }
+ }
+ else // DEFLATE header
+ {
+ var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
+ var level_flags = -1;
+
+ if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
+ level_flags = 0;
+ } else if (s.level < 6) {
+ level_flags = 1;
+ } else if (s.level === 6) {
+ level_flags = 2;
+ } else {
+ level_flags = 3;
+ }
+ header |= (level_flags << 6);
+ if (s.strstart !== 0) { header |= PRESET_DICT; }
+ header += 31 - (header % 31);
+
+ s.status = BUSY_STATE;
+ putShortMSB(s, header);
+
+ /* Save the adler32 of the preset dictionary: */
+ if (s.strstart !== 0) {
+ putShortMSB(s, strm.adler >>> 16);
+ putShortMSB(s, strm.adler & 0xffff);
+ }
+ strm.adler = 1; // adler32(0L, Z_NULL, 0);
+ }
+ }
+
+//#ifdef GZIP
+ if (s.status === EXTRA_STATE) {
+ if (s.gzhead.extra/* != Z_NULL*/) {
+ beg = s.pending; /* start of bytes to update crc */
+
+ while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
+ if (s.pending === s.pending_buf_size) {
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ flush_pending(strm);
+ beg = s.pending;
+ if (s.pending === s.pending_buf_size) {
+ break;
+ }
+ }
+ put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
+ s.gzindex++;
+ }
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ if (s.gzindex === s.gzhead.extra.length) {
+ s.gzindex = 0;
+ s.status = NAME_STATE;
+ }
+ }
+ else {
+ s.status = NAME_STATE;
+ }
+ }
+ if (s.status === NAME_STATE) {
+ if (s.gzhead.name/* != Z_NULL*/) {
+ beg = s.pending; /* start of bytes to update crc */
+ //int val;
+
+ do {
+ if (s.pending === s.pending_buf_size) {
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ flush_pending(strm);
+ beg = s.pending;
+ if (s.pending === s.pending_buf_size) {
+ val = 1;
+ break;
+ }
+ }
+ // JS specific: little magic to add zero terminator to end of string
+ if (s.gzindex < s.gzhead.name.length) {
+ val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
+ } else {
+ val = 0;
+ }
+ put_byte(s, val);
+ } while (val !== 0);
+
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ if (val === 0) {
+ s.gzindex = 0;
+ s.status = COMMENT_STATE;
+ }
+ }
+ else {
+ s.status = COMMENT_STATE;
+ }
+ }
+ if (s.status === COMMENT_STATE) {
+ if (s.gzhead.comment/* != Z_NULL*/) {
+ beg = s.pending; /* start of bytes to update crc */
+ //int val;
+
+ do {
+ if (s.pending === s.pending_buf_size) {
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ flush_pending(strm);
+ beg = s.pending;
+ if (s.pending === s.pending_buf_size) {
+ val = 1;
+ break;
+ }
+ }
+ // JS specific: little magic to add zero terminator to end of string
+ if (s.gzindex < s.gzhead.comment.length) {
+ val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
+ } else {
+ val = 0;
+ }
+ put_byte(s, val);
+ } while (val !== 0);
+
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ if (val === 0) {
+ s.status = HCRC_STATE;
+ }
+ }
+ else {
+ s.status = HCRC_STATE;
+ }
+ }
+ if (s.status === HCRC_STATE) {
+ if (s.gzhead.hcrc) {
+ if (s.pending + 2 > s.pending_buf_size) {
+ flush_pending(strm);
+ }
+ if (s.pending + 2 <= s.pending_buf_size) {
+ put_byte(s, strm.adler & 0xff);
+ put_byte(s, (strm.adler >> 8) & 0xff);
+ strm.adler = 0; //crc32(0L, Z_NULL, 0);
+ s.status = BUSY_STATE;
+ }
+ }
+ else {
+ s.status = BUSY_STATE;
+ }
+ }
+//#endif
+
+ /* Flush as much pending output as possible */
+ if (s.pending !== 0) {
+ flush_pending(strm);
+ if (strm.avail_out === 0) {
+ /* Since avail_out is 0, deflate will be called again with
+ * more output space, but possibly with both pending and
+ * avail_in equal to zero. There won't be anything to do,
+ * but this is not an error situation so make sure we
+ * return OK instead of BUF_ERROR at next call of deflate:
+ */
+ s.last_flush = -1;
+ return Z_OK;
+ }
+
+ /* Make sure there is something to do and avoid duplicate consecutive
+ * flushes. For repeated and useless calls with Z_FINISH, we keep
+ * returning Z_STREAM_END instead of Z_BUF_ERROR.
+ */
+ } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
+ flush !== Z_FINISH) {
+ return err(strm, Z_BUF_ERROR);
+ }
+
+ /* User must not provide more input after the first FINISH: */
+ if (s.status === FINISH_STATE && strm.avail_in !== 0) {
+ return err(strm, Z_BUF_ERROR);
+ }
+
+ /* Start a new block or continue the current one.
+ */
+ if (strm.avail_in !== 0 || s.lookahead !== 0 ||
+ (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
+ var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
+ (s.strategy === Z_RLE ? deflate_rle(s, flush) :
+ configuration_table[s.level].func(s, flush));
+
+ if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
+ s.status = FINISH_STATE;
+ }
+ if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
+ if (strm.avail_out === 0) {
+ s.last_flush = -1;
+ /* avoid BUF_ERROR next call, see above */
+ }
+ return Z_OK;
+ /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+ * of deflate should use the same flush parameter to make sure
+ * that the flush is complete. So we don't have to output an
+ * empty block here, this will be done at next call. This also
+ * ensures that for a very small output buffer, we emit at most
+ * one empty block.
+ */
+ }
+ if (bstate === BS_BLOCK_DONE) {
+ if (flush === Z_PARTIAL_FLUSH) {
+ trees._tr_align(s);
+ }
+ else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+
+ trees._tr_stored_block(s, 0, 0, false);
+ /* For a full flush, this empty block will be recognized
+ * as a special marker by inflate_sync().
+ */
+ if (flush === Z_FULL_FLUSH) {
+ /*** CLEAR_HASH(s); ***/ /* forget history */
+ zero(s.head); // Fill with NIL (= 0);
+
+ if (s.lookahead === 0) {
+ s.strstart = 0;
+ s.block_start = 0;
+ s.insert = 0;
+ }
+ }
+ }
+ flush_pending(strm);
+ if (strm.avail_out === 0) {
+ s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+ return Z_OK;
+ }
+ }
+ }
+ //Assert(strm->avail_out > 0, "bug2");
+ //if (strm.avail_out <= 0) { throw new Error("bug2");}
+
+ if (flush !== Z_FINISH) { return Z_OK; }
+ if (s.wrap <= 0) { return Z_STREAM_END; }
+
+ /* Write the trailer */
+ if (s.wrap === 2) {
+ put_byte(s, strm.adler & 0xff);
+ put_byte(s, (strm.adler >> 8) & 0xff);
+ put_byte(s, (strm.adler >> 16) & 0xff);
+ put_byte(s, (strm.adler >> 24) & 0xff);
+ put_byte(s, strm.total_in & 0xff);
+ put_byte(s, (strm.total_in >> 8) & 0xff);
+ put_byte(s, (strm.total_in >> 16) & 0xff);
+ put_byte(s, (strm.total_in >> 24) & 0xff);
+ }
+ else
+ {
+ putShortMSB(s, strm.adler >>> 16);
+ putShortMSB(s, strm.adler & 0xffff);
+ }
+
+ flush_pending(strm);
+ /* If avail_out is zero, the application will call deflate again
+ * to flush the rest.
+ */
+ if (s.wrap > 0) { s.wrap = -s.wrap; }
+ /* write the trailer only once! */
+ return s.pending !== 0 ? Z_OK : Z_STREAM_END;
+}
+
+function deflateEnd(strm) {
+ var status;
+
+ if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+ return Z_STREAM_ERROR;
+ }
+
+ status = strm.state.status;
+ if (status !== INIT_STATE &&
+ status !== EXTRA_STATE &&
+ status !== NAME_STATE &&
+ status !== COMMENT_STATE &&
+ status !== HCRC_STATE &&
+ status !== BUSY_STATE &&
+ status !== FINISH_STATE
+ ) {
+ return err(strm, Z_STREAM_ERROR);
+ }
+
+ strm.state = null;
+
+ return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
+}
+
+
+/* =========================================================================
+ * Initializes the compression dictionary from the given byte
+ * sequence without producing any compressed output.
+ */
+function deflateSetDictionary(strm, dictionary) {
+ var dictLength = dictionary.length;
+
+ var s;
+ var str, n;
+ var wrap;
+ var avail;
+ var next;
+ var input;
+ var tmpDict;
+
+ if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+ return Z_STREAM_ERROR;
+ }
+
+ s = strm.state;
+ wrap = s.wrap;
+
+ if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
+ return Z_STREAM_ERROR;
+ }
+
+ /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+ if (wrap === 1) {
+ /* adler32(strm->adler, dictionary, dictLength); */
+ strm.adler = adler32(strm.adler, dictionary, dictLength, 0);
+ }
+
+ s.wrap = 0; /* avoid computing Adler-32 in read_buf */
+
+ /* if dictionary would fill window, just replace the history */
+ if (dictLength >= s.w_size) {
+ if (wrap === 0) { /* already empty otherwise */
+ /*** CLEAR_HASH(s); ***/
+ zero(s.head); // Fill with NIL (= 0);
+ s.strstart = 0;
+ s.block_start = 0;
+ s.insert = 0;
+ }
+ /* use the tail */
+ // dictionary = dictionary.slice(dictLength - s.w_size);
+ tmpDict = new utils.Buf8(s.w_size);
+ utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);
+ dictionary = tmpDict;
+ dictLength = s.w_size;
+ }
+ /* insert dictionary into window and hash */
+ avail = strm.avail_in;
+ next = strm.next_in;
+ input = strm.input;
+ strm.avail_in = dictLength;
+ strm.next_in = 0;
+ strm.input = dictionary;
+ fill_window(s);
+ while (s.lookahead >= MIN_MATCH) {
+ str = s.strstart;
+ n = s.lookahead - (MIN_MATCH - 1);
+ do {
+ /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
+
+ s.prev[str & s.w_mask] = s.head[s.ins_h];
+
+ s.head[s.ins_h] = str;
+ str++;
+ } while (--n);
+ s.strstart = str;
+ s.lookahead = MIN_MATCH - 1;
+ fill_window(s);
+ }
+ s.strstart += s.lookahead;
+ s.block_start = s.strstart;
+ s.insert = s.lookahead;
+ s.lookahead = 0;
+ s.match_length = s.prev_length = MIN_MATCH - 1;
+ s.match_available = 0;
+ strm.next_in = next;
+ strm.input = input;
+ strm.avail_in = avail;
+ s.wrap = wrap;
+ return Z_OK;
+}
+
+
+export { deflateInit, deflateInit2, deflateReset, deflateResetKeep, deflateSetHeader, deflate, deflateEnd, deflateSetDictionary };
+export var deflateInfo = 'pako deflate (from Nodeca project)';
+
+/* Not implemented
+exports.deflateBound = deflateBound;
+exports.deflateCopy = deflateCopy;
+exports.deflateParams = deflateParams;
+exports.deflatePending = deflatePending;
+exports.deflatePrime = deflatePrime;
+exports.deflateTune = deflateTune;
+*/
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/gzheader.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/gzheader.js
new file mode 100644
index 0000000..2ec586d
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/gzheader.js
@@ -0,0 +1,35 @@
+export default function GZheader() {
+ /* true if compressed data believed to be text */
+ this.text = 0;
+ /* modification time */
+ this.time = 0;
+ /* extra flags (not used when writing a gzip file) */
+ this.xflags = 0;
+ /* operating system */
+ this.os = 0;
+ /* pointer to extra field or Z_NULL if none */
+ this.extra = null;
+ /* extra field length (valid if extra != Z_NULL) */
+ this.extra_len = 0; // Actually, we don't need it in JS,
+ // but leave for few code modifications
+
+ //
+ // Setup limits is not necessary because in js we should not preallocate memory
+ // for inflate use constant limit in 65536 bytes
+ //
+
+ /* space at extra (only when reading header) */
+ // this.extra_max = 0;
+ /* pointer to zero-terminated file name or Z_NULL */
+ this.name = '';
+ /* space at name (only when reading header) */
+ // this.name_max = 0;
+ /* pointer to zero-terminated comment or Z_NULL */
+ this.comment = '';
+ /* space at comment (only when reading header) */
+ // this.comm_max = 0;
+ /* true if there was or will be a header crc */
+ this.hcrc = 0;
+ /* true when done reading gzip header (not used when writing a gzip file) */
+ this.done = false;
+}
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/inffast.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inffast.js
new file mode 100644
index 0000000..889dcc7
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inffast.js
@@ -0,0 +1,324 @@
+// See state defs from inflate.js
+var BAD = 30; /* got a data error -- remain here until reset */
+var TYPE = 12; /* i: waiting for type bits, including last-flag bit */
+
+/*
+ Decode literal, length, and distance codes and write out the resulting
+ literal and match bytes until either not enough input or output is
+ available, an end-of-block is encountered, or a data error is encountered.
+ When large enough input and output buffers are supplied to inflate(), for
+ example, a 16K input buffer and a 64K output buffer, more than 95% of the
+ inflate execution time is spent in this routine.
+
+ Entry assumptions:
+
+ state.mode === LEN
+ strm.avail_in >= 6
+ strm.avail_out >= 258
+ start >= strm.avail_out
+ state.bits < 8
+
+ On return, state.mode is one of:
+
+ LEN -- ran out of enough output space or enough available input
+ TYPE -- reached end of block code, inflate() to interpret next block
+ BAD -- error in block data
+
+ Notes:
+
+ - The maximum input bits used by a length/distance pair is 15 bits for the
+ length code, 5 bits for the length extra, 15 bits for the distance code,
+ and 13 bits for the distance extra. This totals 48 bits, or six bytes.
+ Therefore if strm.avail_in >= 6, then there is enough input to avoid
+ checking for available input while decoding.
+
+ - The maximum bytes that a single length/distance pair can output is 258
+ bytes, which is the maximum length that can be coded. inflate_fast()
+ requires strm.avail_out >= 258 for each loop to avoid checking for
+ output space.
+ */
+export default function inflate_fast(strm, start) {
+ var state;
+ var _in; /* local strm.input */
+ var last; /* have enough input while in < last */
+ var _out; /* local strm.output */
+ var beg; /* inflate()'s initial strm.output */
+ var end; /* while out < end, enough space available */
+//#ifdef INFLATE_STRICT
+ var dmax; /* maximum distance from zlib header */
+//#endif
+ var wsize; /* window size or zero if not using window */
+ var whave; /* valid bytes in the window */
+ var wnext; /* window write index */
+ // Use `s_window` instead `window`, avoid conflict with instrumentation tools
+ var s_window; /* allocated sliding window, if wsize != 0 */
+ var hold; /* local strm.hold */
+ var bits; /* local strm.bits */
+ var lcode; /* local strm.lencode */
+ var dcode; /* local strm.distcode */
+ var lmask; /* mask for first level of length codes */
+ var dmask; /* mask for first level of distance codes */
+ var here; /* retrieved table entry */
+ var op; /* code bits, operation, extra bits, or */
+ /* window position, window bytes to copy */
+ var len; /* match length, unused bytes */
+ var dist; /* match distance */
+ var from; /* where to copy match from */
+ var from_source;
+
+
+ var input, output; // JS specific, because we have no pointers
+
+ /* copy state to local variables */
+ state = strm.state;
+ //here = state.here;
+ _in = strm.next_in;
+ input = strm.input;
+ last = _in + (strm.avail_in - 5);
+ _out = strm.next_out;
+ output = strm.output;
+ beg = _out - (start - strm.avail_out);
+ end = _out + (strm.avail_out - 257);
+//#ifdef INFLATE_STRICT
+ dmax = state.dmax;
+//#endif
+ wsize = state.wsize;
+ whave = state.whave;
+ wnext = state.wnext;
+ s_window = state.window;
+ hold = state.hold;
+ bits = state.bits;
+ lcode = state.lencode;
+ dcode = state.distcode;
+ lmask = (1 << state.lenbits) - 1;
+ dmask = (1 << state.distbits) - 1;
+
+
+ /* decode literals and length/distances until end-of-block or not enough
+ input data or output space */
+
+ top:
+ do {
+ if (bits < 15) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+
+ here = lcode[hold & lmask];
+
+ dolen:
+ for (;;) { // Goto emulation
+ op = here >>> 24/*here.bits*/;
+ hold >>>= op;
+ bits -= op;
+ op = (here >>> 16) & 0xff/*here.op*/;
+ if (op === 0) { /* literal */
+ //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ // "inflate: literal '%c'\n" :
+ // "inflate: literal 0x%02x\n", here.val));
+ output[_out++] = here & 0xffff/*here.val*/;
+ }
+ else if (op & 16) { /* length base */
+ len = here & 0xffff/*here.val*/;
+ op &= 15; /* number of extra bits */
+ if (op) {
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ len += hold & ((1 << op) - 1);
+ hold >>>= op;
+ bits -= op;
+ }
+ //Tracevv((stderr, "inflate: length %u\n", len));
+ if (bits < 15) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ here = dcode[hold & dmask];
+
+ dodist:
+ for (;;) { // goto emulation
+ op = here >>> 24/*here.bits*/;
+ hold >>>= op;
+ bits -= op;
+ op = (here >>> 16) & 0xff/*here.op*/;
+
+ if (op & 16) { /* distance base */
+ dist = here & 0xffff/*here.val*/;
+ op &= 15; /* number of extra bits */
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ }
+ dist += hold & ((1 << op) - 1);
+//#ifdef INFLATE_STRICT
+ if (dist > dmax) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break top;
+ }
+//#endif
+ hold >>>= op;
+ bits -= op;
+ //Tracevv((stderr, "inflate: distance %u\n", dist));
+ op = _out - beg; /* max distance in output */
+ if (dist > op) { /* see if copy from window */
+ op = dist - op; /* distance back in window */
+ if (op > whave) {
+ if (state.sane) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break top;
+ }
+
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+// if (len <= op - whave) {
+// do {
+// output[_out++] = 0;
+// } while (--len);
+// continue top;
+// }
+// len -= op - whave;
+// do {
+// output[_out++] = 0;
+// } while (--op > whave);
+// if (op === 0) {
+// from = _out - dist;
+// do {
+// output[_out++] = output[from++];
+// } while (--len);
+// continue top;
+// }
+//#endif
+ }
+ from = 0; // window index
+ from_source = s_window;
+ if (wnext === 0) { /* very common case */
+ from += wsize - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ else if (wnext < op) { /* wrap around window */
+ from += wsize + wnext - op;
+ op -= wnext;
+ if (op < len) { /* some from end of window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = 0;
+ if (wnext < len) { /* some from start of window */
+ op = wnext;
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ }
+ else { /* contiguous in window */
+ from += wnext - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ while (len > 2) {
+ output[_out++] = from_source[from++];
+ output[_out++] = from_source[from++];
+ output[_out++] = from_source[from++];
+ len -= 3;
+ }
+ if (len) {
+ output[_out++] = from_source[from++];
+ if (len > 1) {
+ output[_out++] = from_source[from++];
+ }
+ }
+ }
+ else {
+ from = _out - dist; /* copy direct from output */
+ do { /* minimum length is three */
+ output[_out++] = output[from++];
+ output[_out++] = output[from++];
+ output[_out++] = output[from++];
+ len -= 3;
+ } while (len > 2);
+ if (len) {
+ output[_out++] = output[from++];
+ if (len > 1) {
+ output[_out++] = output[from++];
+ }
+ }
+ }
+ }
+ else if ((op & 64) === 0) { /* 2nd level distance code */
+ here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+ continue dodist;
+ }
+ else {
+ strm.msg = 'invalid distance code';
+ state.mode = BAD;
+ break top;
+ }
+
+ break; // need to emulate goto via "continue"
+ }
+ }
+ else if ((op & 64) === 0) { /* 2nd level length code */
+ here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+ continue dolen;
+ }
+ else if (op & 32) { /* end-of-block */
+ //Tracevv((stderr, "inflate: end of block\n"));
+ state.mode = TYPE;
+ break top;
+ }
+ else {
+ strm.msg = 'invalid literal/length code';
+ state.mode = BAD;
+ break top;
+ }
+
+ break; // need to emulate goto via "continue"
+ }
+ } while (_in < last && _out < end);
+
+ /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+ len = bits >> 3;
+ _in -= len;
+ bits -= len << 3;
+ hold &= (1 << bits) - 1;
+
+ /* update state and return */
+ strm.next_in = _in;
+ strm.next_out = _out;
+ strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
+ strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
+ state.hold = hold;
+ state.bits = bits;
+ return;
+};
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/inflate.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inflate.js
new file mode 100644
index 0000000..b79b396
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inflate.js
@@ -0,0 +1,1527 @@
+import * as utils from "../utils/common.js";
+import adler32 from "./adler32.js";
+import crc32 from "./crc32.js";
+import inflate_fast from "./inffast.js";
+import inflate_table from "./inftrees.js";
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+/* Allowed flush values; see deflate() and inflate() below for details */
+//var Z_NO_FLUSH = 0;
+//var Z_PARTIAL_FLUSH = 1;
+//var Z_SYNC_FLUSH = 2;
+//var Z_FULL_FLUSH = 3;
+var Z_FINISH = 4;
+var Z_BLOCK = 5;
+var Z_TREES = 6;
+
+
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+var Z_OK = 0;
+var Z_STREAM_END = 1;
+var Z_NEED_DICT = 2;
+//var Z_ERRNO = -1;
+var Z_STREAM_ERROR = -2;
+var Z_DATA_ERROR = -3;
+var Z_MEM_ERROR = -4;
+var Z_BUF_ERROR = -5;
+//var Z_VERSION_ERROR = -6;
+
+/* The deflate compression method */
+var Z_DEFLATED = 8;
+
+
+/* STATES ====================================================================*/
+/* ===========================================================================*/
+
+
+var HEAD = 1; /* i: waiting for magic header */
+var FLAGS = 2; /* i: waiting for method and flags (gzip) */
+var TIME = 3; /* i: waiting for modification time (gzip) */
+var OS = 4; /* i: waiting for extra flags and operating system (gzip) */
+var EXLEN = 5; /* i: waiting for extra length (gzip) */
+var EXTRA = 6; /* i: waiting for extra bytes (gzip) */
+var NAME = 7; /* i: waiting for end of file name (gzip) */
+var COMMENT = 8; /* i: waiting for end of comment (gzip) */
+var HCRC = 9; /* i: waiting for header crc (gzip) */
+var DICTID = 10; /* i: waiting for dictionary check value */
+var DICT = 11; /* waiting for inflateSetDictionary() call */
+var TYPE = 12; /* i: waiting for type bits, including last-flag bit */
+var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */
+var STORED = 14; /* i: waiting for stored size (length and complement) */
+var COPY_ = 15; /* i/o: same as COPY below, but only first time in */
+var COPY = 16; /* i/o: waiting for input or output to copy stored block */
+var TABLE = 17; /* i: waiting for dynamic block table lengths */
+var LENLENS = 18; /* i: waiting for code length code lengths */
+var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */
+var LEN_ = 20; /* i: same as LEN below, but only first time in */
+var LEN = 21; /* i: waiting for length/lit/eob code */
+var LENEXT = 22; /* i: waiting for length extra bits */
+var DIST = 23; /* i: waiting for distance code */
+var DISTEXT = 24; /* i: waiting for distance extra bits */
+var MATCH = 25; /* o: waiting for output space to copy string */
+var LIT = 26; /* o: waiting for output space to write literal */
+var CHECK = 27; /* i: waiting for 32-bit check value */
+var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */
+var DONE = 29; /* finished check, done -- remain here until reset */
+var BAD = 30; /* got a data error -- remain here until reset */
+var MEM = 31; /* got an inflate() memory error -- remain here until reset */
+var SYNC = 32; /* looking for synchronization bytes to restart inflate() */
+
+/* ===========================================================================*/
+
+
+
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+var MAX_WBITS = 15;
+/* 32K LZ77 window */
+var DEF_WBITS = MAX_WBITS;
+
+
+function zswap32(q) {
+ return (((q >>> 24) & 0xff) +
+ ((q >>> 8) & 0xff00) +
+ ((q & 0xff00) << 8) +
+ ((q & 0xff) << 24));
+}
+
+
+function InflateState() {
+ this.mode = 0; /* current inflate mode */
+ this.last = false; /* true if processing last block */
+ this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */
+ this.havedict = false; /* true if dictionary provided */
+ this.flags = 0; /* gzip header method and flags (0 if zlib) */
+ this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */
+ this.check = 0; /* protected copy of check value */
+ this.total = 0; /* protected copy of output count */
+ // TODO: may be {}
+ this.head = null; /* where to save gzip header information */
+
+ /* sliding window */
+ this.wbits = 0; /* log base 2 of requested window size */
+ this.wsize = 0; /* window size or zero if not using window */
+ this.whave = 0; /* valid bytes in the window */
+ this.wnext = 0; /* window write index */
+ this.window = null; /* allocated sliding window, if needed */
+
+ /* bit accumulator */
+ this.hold = 0; /* input bit accumulator */
+ this.bits = 0; /* number of bits in "in" */
+
+ /* for string and stored block copying */
+ this.length = 0; /* literal or length of data to copy */
+ this.offset = 0; /* distance back to copy string from */
+
+ /* for table and code decoding */
+ this.extra = 0; /* extra bits needed */
+
+ /* fixed and dynamic code tables */
+ this.lencode = null; /* starting table for length/literal codes */
+ this.distcode = null; /* starting table for distance codes */
+ this.lenbits = 0; /* index bits for lencode */
+ this.distbits = 0; /* index bits for distcode */
+
+ /* dynamic table building */
+ this.ncode = 0; /* number of code length code lengths */
+ this.nlen = 0; /* number of length code lengths */
+ this.ndist = 0; /* number of distance code lengths */
+ this.have = 0; /* number of code lengths in lens[] */
+ this.next = null; /* next available space in codes[] */
+
+ this.lens = new utils.Buf16(320); /* temporary storage for code lengths */
+ this.work = new utils.Buf16(288); /* work area for code table building */
+
+ /*
+ because we don't have pointers in js, we use lencode and distcode directly
+ as buffers so we don't need codes
+ */
+ //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */
+ this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */
+ this.distdyn = null; /* dynamic table for distance codes (JS specific) */
+ this.sane = 0; /* if false, allow invalid distance too far */
+ this.back = 0; /* bits back of last unprocessed length/lit */
+ this.was = 0; /* initial length of match */
+}
+
+function inflateResetKeep(strm) {
+ var state;
+
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+ strm.total_in = strm.total_out = state.total = 0;
+ strm.msg = ''; /*Z_NULL*/
+ if (state.wrap) { /* to support ill-conceived Java test suite */
+ strm.adler = state.wrap & 1;
+ }
+ state.mode = HEAD;
+ state.last = 0;
+ state.havedict = 0;
+ state.dmax = 32768;
+ state.head = null/*Z_NULL*/;
+ state.hold = 0;
+ state.bits = 0;
+ //state.lencode = state.distcode = state.next = state.codes;
+ state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
+ state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);
+
+ state.sane = 1;
+ state.back = -1;
+ //Tracev((stderr, "inflate: reset\n"));
+ return Z_OK;
+}
+
+function inflateReset(strm) {
+ var state;
+
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+ state.wsize = 0;
+ state.whave = 0;
+ state.wnext = 0;
+ return inflateResetKeep(strm);
+
+}
+
+function inflateReset2(strm, windowBits) {
+ var wrap;
+ var state;
+
+ /* get the state */
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+
+ /* extract wrap request from windowBits parameter */
+ if (windowBits < 0) {
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+ else {
+ wrap = (windowBits >> 4) + 1;
+ if (windowBits < 48) {
+ windowBits &= 15;
+ }
+ }
+
+ /* set number of window bits, free window if different */
+ if (windowBits && (windowBits < 8 || windowBits > 15)) {
+ return Z_STREAM_ERROR;
+ }
+ if (state.window !== null && state.wbits !== windowBits) {
+ state.window = null;
+ }
+
+ /* update state and reset the rest of it */
+ state.wrap = wrap;
+ state.wbits = windowBits;
+ return inflateReset(strm);
+}
+
+function inflateInit2(strm, windowBits) {
+ var ret;
+ var state;
+
+ if (!strm) { return Z_STREAM_ERROR; }
+ //strm.msg = Z_NULL; /* in case we return an error */
+
+ state = new InflateState();
+
+ //if (state === Z_NULL) return Z_MEM_ERROR;
+ //Tracev((stderr, "inflate: allocated\n"));
+ strm.state = state;
+ state.window = null/*Z_NULL*/;
+ ret = inflateReset2(strm, windowBits);
+ if (ret !== Z_OK) {
+ strm.state = null/*Z_NULL*/;
+ }
+ return ret;
+}
+
+function inflateInit(strm) {
+ return inflateInit2(strm, DEF_WBITS);
+}
+
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding. Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter. This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time. However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+var virgin = true;
+
+var lenfix, distfix; // We have no pointers in JS, so keep tables separate
+
+function fixedtables(state) {
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ var sym;
+
+ lenfix = new utils.Buf32(512);
+ distfix = new utils.Buf32(32);
+
+ /* literal/length table */
+ sym = 0;
+ while (sym < 144) { state.lens[sym++] = 8; }
+ while (sym < 256) { state.lens[sym++] = 9; }
+ while (sym < 280) { state.lens[sym++] = 7; }
+ while (sym < 288) { state.lens[sym++] = 8; }
+
+ inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 });
+
+ /* distance table */
+ sym = 0;
+ while (sym < 32) { state.lens[sym++] = 5; }
+
+ inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 });
+
+ /* do this just once */
+ virgin = false;
+ }
+
+ state.lencode = lenfix;
+ state.lenbits = 9;
+ state.distcode = distfix;
+ state.distbits = 5;
+}
+
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning. If window does not exist yet, create it. This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+function updatewindow(strm, src, end, copy) {
+ var dist;
+ var state = strm.state;
+
+ /* if it hasn't been done already, allocate space for the window */
+ if (state.window === null) {
+ state.wsize = 1 << state.wbits;
+ state.wnext = 0;
+ state.whave = 0;
+
+ state.window = new utils.Buf8(state.wsize);
+ }
+
+ /* copy state->wsize or less output bytes into the circular window */
+ if (copy >= state.wsize) {
+ utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0);
+ state.wnext = 0;
+ state.whave = state.wsize;
+ }
+ else {
+ dist = state.wsize - state.wnext;
+ if (dist > copy) {
+ dist = copy;
+ }
+ //zmemcpy(state->window + state->wnext, end - copy, dist);
+ utils.arraySet(state.window, src, end - copy, dist, state.wnext);
+ copy -= dist;
+ if (copy) {
+ //zmemcpy(state->window, end - copy, copy);
+ utils.arraySet(state.window, src, end - copy, copy, 0);
+ state.wnext = copy;
+ state.whave = state.wsize;
+ }
+ else {
+ state.wnext += dist;
+ if (state.wnext === state.wsize) { state.wnext = 0; }
+ if (state.whave < state.wsize) { state.whave += dist; }
+ }
+ }
+ return 0;
+}
+
+function inflate(strm, flush) {
+ var state;
+ var input, output; // input/output buffers
+ var next; /* next input INDEX */
+ var put; /* next output INDEX */
+ var have, left; /* available input and output */
+ var hold; /* bit buffer */
+ var bits; /* bits in bit buffer */
+ var _in, _out; /* save starting available input and output */
+ var copy; /* number of stored or match bytes to copy */
+ var from; /* where to copy match bytes from */
+ var from_source;
+ var here = 0; /* current decoding table entry */
+ var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
+ //var last; /* parent table entry */
+ var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
+ var len; /* length to copy for repeats, bits to drop */
+ var ret; /* return code */
+ var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */
+ var opts;
+
+ var n; // temporary var for NEED_BITS
+
+ var order = /* permutation of code lengths */
+ [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];
+
+
+ if (!strm || !strm.state || !strm.output ||
+ (!strm.input && strm.avail_in !== 0)) {
+ return Z_STREAM_ERROR;
+ }
+
+ state = strm.state;
+ if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */
+
+
+ //--- LOAD() ---
+ put = strm.next_out;
+ output = strm.output;
+ left = strm.avail_out;
+ next = strm.next_in;
+ input = strm.input;
+ have = strm.avail_in;
+ hold = state.hold;
+ bits = state.bits;
+ //---
+
+ _in = have;
+ _out = left;
+ ret = Z_OK;
+
+ inf_leave: // goto emulation
+ for (;;) {
+ switch (state.mode) {
+ case HEAD:
+ if (state.wrap === 0) {
+ state.mode = TYPEDO;
+ break;
+ }
+ //=== NEEDBITS(16);
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */
+ state.check = 0/*crc32(0L, Z_NULL, 0)*/;
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = FLAGS;
+ break;
+ }
+ state.flags = 0; /* expect zlib header */
+ if (state.head) {
+ state.head.done = false;
+ }
+ if (!(state.wrap & 1) || /* check if zlib header allowed */
+ (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
+ strm.msg = 'incorrect header check';
+ state.mode = BAD;
+ break;
+ }
+ if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
+ strm.msg = 'unknown compression method';
+ state.mode = BAD;
+ break;
+ }
+ //--- DROPBITS(4) ---//
+ hold >>>= 4;
+ bits -= 4;
+ //---//
+ len = (hold & 0x0f)/*BITS(4)*/ + 8;
+ if (state.wbits === 0) {
+ state.wbits = len;
+ }
+ else if (len > state.wbits) {
+ strm.msg = 'invalid window size';
+ state.mode = BAD;
+ break;
+ }
+ state.dmax = 1 << len;
+ //Tracev((stderr, "inflate: zlib header ok\n"));
+ strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+ state.mode = hold & 0x200 ? DICTID : TYPE;
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ break;
+ case FLAGS:
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.flags = hold;
+ if ((state.flags & 0xff) !== Z_DEFLATED) {
+ strm.msg = 'unknown compression method';
+ state.mode = BAD;
+ break;
+ }
+ if (state.flags & 0xe000) {
+ strm.msg = 'unknown header flags set';
+ state.mode = BAD;
+ break;
+ }
+ if (state.head) {
+ state.head.text = ((hold >> 8) & 1);
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = TIME;
+ /* falls through */
+ case TIME:
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (state.head) {
+ state.head.time = hold;
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC4(state.check, hold)
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ hbuf[2] = (hold >>> 16) & 0xff;
+ hbuf[3] = (hold >>> 24) & 0xff;
+ state.check = crc32(state.check, hbuf, 4, 0);
+ //===
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = OS;
+ /* falls through */
+ case OS:
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (state.head) {
+ state.head.xflags = (hold & 0xff);
+ state.head.os = (hold >> 8);
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = EXLEN;
+ /* falls through */
+ case EXLEN:
+ if (state.flags & 0x0400) {
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.length = hold;
+ if (state.head) {
+ state.head.extra_len = hold;
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ }
+ else if (state.head) {
+ state.head.extra = null/*Z_NULL*/;
+ }
+ state.mode = EXTRA;
+ /* falls through */
+ case EXTRA:
+ if (state.flags & 0x0400) {
+ copy = state.length;
+ if (copy > have) { copy = have; }
+ if (copy) {
+ if (state.head) {
+ len = state.head.extra_len - state.length;
+ if (!state.head.extra) {
+ // Use untyped array for more conveniend processing later
+ state.head.extra = new Array(state.head.extra_len);
+ }
+ utils.arraySet(
+ state.head.extra,
+ input,
+ next,
+ // extra field is limited to 65536 bytes
+ // - no need for additional size check
+ copy,
+ /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
+ len
+ );
+ //zmemcpy(state.head.extra + len, next,
+ // len + copy > state.head.extra_max ?
+ // state.head.extra_max - len : copy);
+ }
+ if (state.flags & 0x0200) {
+ state.check = crc32(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ state.length -= copy;
+ }
+ if (state.length) { break inf_leave; }
+ }
+ state.length = 0;
+ state.mode = NAME;
+ /* falls through */
+ case NAME:
+ if (state.flags & 0x0800) {
+ if (have === 0) { break inf_leave; }
+ copy = 0;
+ do {
+ // TODO: 2 or 1 bytes?
+ len = input[next + copy++];
+ /* use constant limit because in js we should not preallocate memory */
+ if (state.head && len &&
+ (state.length < 65536 /*state.head.name_max*/)) {
+ state.head.name += String.fromCharCode(len);
+ }
+ } while (len && copy < have);
+
+ if (state.flags & 0x0200) {
+ state.check = crc32(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ if (len) { break inf_leave; }
+ }
+ else if (state.head) {
+ state.head.name = null;
+ }
+ state.length = 0;
+ state.mode = COMMENT;
+ /* falls through */
+ case COMMENT:
+ if (state.flags & 0x1000) {
+ if (have === 0) { break inf_leave; }
+ copy = 0;
+ do {
+ len = input[next + copy++];
+ /* use constant limit because in js we should not preallocate memory */
+ if (state.head && len &&
+ (state.length < 65536 /*state.head.comm_max*/)) {
+ state.head.comment += String.fromCharCode(len);
+ }
+ } while (len && copy < have);
+ if (state.flags & 0x0200) {
+ state.check = crc32(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ if (len) { break inf_leave; }
+ }
+ else if (state.head) {
+ state.head.comment = null;
+ }
+ state.mode = HCRC;
+ /* falls through */
+ case HCRC:
+ if (state.flags & 0x0200) {
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (hold !== (state.check & 0xffff)) {
+ strm.msg = 'header crc mismatch';
+ state.mode = BAD;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ }
+ if (state.head) {
+ state.head.hcrc = ((state.flags >> 9) & 1);
+ state.head.done = true;
+ }
+ strm.adler = state.check = 0;
+ state.mode = TYPE;
+ break;
+ case DICTID:
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ strm.adler = state.check = zswap32(hold);
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = DICT;
+ /* falls through */
+ case DICT:
+ if (state.havedict === 0) {
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+ return Z_NEED_DICT;
+ }
+ strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+ state.mode = TYPE;
+ /* falls through */
+ case TYPE:
+ if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case TYPEDO:
+ if (state.last) {
+ //--- BYTEBITS() ---//
+ hold >>>= bits & 7;
+ bits -= bits & 7;
+ //---//
+ state.mode = CHECK;
+ break;
+ }
+ //=== NEEDBITS(3); */
+ while (bits < 3) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.last = (hold & 0x01)/*BITS(1)*/;
+ //--- DROPBITS(1) ---//
+ hold >>>= 1;
+ bits -= 1;
+ //---//
+
+ switch ((hold & 0x03)/*BITS(2)*/) {
+ case 0: /* stored block */
+ //Tracev((stderr, "inflate: stored block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = STORED;
+ break;
+ case 1: /* fixed block */
+ fixedtables(state);
+ //Tracev((stderr, "inflate: fixed codes block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = LEN_; /* decode codes */
+ if (flush === Z_TREES) {
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ break inf_leave;
+ }
+ break;
+ case 2: /* dynamic block */
+ //Tracev((stderr, "inflate: dynamic codes block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = TABLE;
+ break;
+ case 3:
+ strm.msg = 'invalid block type';
+ state.mode = BAD;
+ }
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ break;
+ case STORED:
+ //--- BYTEBITS() ---// /* go to byte boundary */
+ hold >>>= bits & 7;
+ bits -= bits & 7;
+ //---//
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
+ strm.msg = 'invalid stored block lengths';
+ state.mode = BAD;
+ break;
+ }
+ state.length = hold & 0xffff;
+ //Tracev((stderr, "inflate: stored length %u\n",
+ // state.length));
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = COPY_;
+ if (flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case COPY_:
+ state.mode = COPY;
+ /* falls through */
+ case COPY:
+ copy = state.length;
+ if (copy) {
+ if (copy > have) { copy = have; }
+ if (copy > left) { copy = left; }
+ if (copy === 0) { break inf_leave; }
+ //--- zmemcpy(put, next, copy); ---
+ utils.arraySet(output, input, next, copy, put);
+ //---//
+ have -= copy;
+ next += copy;
+ left -= copy;
+ put += copy;
+ state.length -= copy;
+ break;
+ }
+ //Tracev((stderr, "inflate: stored end\n"));
+ state.mode = TYPE;
+ break;
+ case TABLE:
+ //=== NEEDBITS(14); */
+ while (bits < 14) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
+ //--- DROPBITS(5) ---//
+ hold >>>= 5;
+ bits -= 5;
+ //---//
+ state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
+ //--- DROPBITS(5) ---//
+ hold >>>= 5;
+ bits -= 5;
+ //---//
+ state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
+ //--- DROPBITS(4) ---//
+ hold >>>= 4;
+ bits -= 4;
+ //---//
+//#ifndef PKZIP_BUG_WORKAROUND
+ if (state.nlen > 286 || state.ndist > 30) {
+ strm.msg = 'too many length or distance symbols';
+ state.mode = BAD;
+ break;
+ }
+//#endif
+ //Tracev((stderr, "inflate: table sizes ok\n"));
+ state.have = 0;
+ state.mode = LENLENS;
+ /* falls through */
+ case LENLENS:
+ while (state.have < state.ncode) {
+ //=== NEEDBITS(3);
+ while (bits < 3) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
+ //--- DROPBITS(3) ---//
+ hold >>>= 3;
+ bits -= 3;
+ //---//
+ }
+ while (state.have < 19) {
+ state.lens[order[state.have++]] = 0;
+ }
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ //state.next = state.codes;
+ //state.lencode = state.next;
+ // Switch to use dynamic table
+ state.lencode = state.lendyn;
+ state.lenbits = 7;
+
+ opts = { bits: state.lenbits };
+ ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
+ state.lenbits = opts.bits;
+
+ if (ret) {
+ strm.msg = 'invalid code lengths set';
+ state.mode = BAD;
+ break;
+ }
+ //Tracev((stderr, "inflate: code lengths ok\n"));
+ state.have = 0;
+ state.mode = CODELENS;
+ /* falls through */
+ case CODELENS:
+ while (state.have < state.nlen + state.ndist) {
+ for (;;) {
+ here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if (here_val < 16) {
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.lens[state.have++] = here_val;
+ }
+ else {
+ if (here_val === 16) {
+ //=== NEEDBITS(here.bits + 2);
+ n = here_bits + 2;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ if (state.have === 0) {
+ strm.msg = 'invalid bit length repeat';
+ state.mode = BAD;
+ break;
+ }
+ len = state.lens[state.have - 1];
+ copy = 3 + (hold & 0x03);//BITS(2);
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ }
+ else if (here_val === 17) {
+ //=== NEEDBITS(here.bits + 3);
+ n = here_bits + 3;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ len = 0;
+ copy = 3 + (hold & 0x07);//BITS(3);
+ //--- DROPBITS(3) ---//
+ hold >>>= 3;
+ bits -= 3;
+ //---//
+ }
+ else {
+ //=== NEEDBITS(here.bits + 7);
+ n = here_bits + 7;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ len = 0;
+ copy = 11 + (hold & 0x7f);//BITS(7);
+ //--- DROPBITS(7) ---//
+ hold >>>= 7;
+ bits -= 7;
+ //---//
+ }
+ if (state.have + copy > state.nlen + state.ndist) {
+ strm.msg = 'invalid bit length repeat';
+ state.mode = BAD;
+ break;
+ }
+ while (copy--) {
+ state.lens[state.have++] = len;
+ }
+ }
+ }
+
+ /* handle error breaks in while */
+ if (state.mode === BAD) { break; }
+
+ /* check for end-of-block code (better have one) */
+ if (state.lens[256] === 0) {
+ strm.msg = 'invalid code -- missing end-of-block';
+ state.mode = BAD;
+ break;
+ }
+
+ /* build code tables -- note: do not change the lenbits or distbits
+ values here (9 and 6) without reading the comments in inftrees.h
+ concerning the ENOUGH constants, which depend on those values */
+ state.lenbits = 9;
+
+ opts = { bits: state.lenbits };
+ ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ // state.next_index = opts.table_index;
+ state.lenbits = opts.bits;
+ // state.lencode = state.next;
+
+ if (ret) {
+ strm.msg = 'invalid literal/lengths set';
+ state.mode = BAD;
+ break;
+ }
+
+ state.distbits = 6;
+ //state.distcode.copy(state.codes);
+ // Switch to use dynamic table
+ state.distcode = state.distdyn;
+ opts = { bits: state.distbits };
+ ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ // state.next_index = opts.table_index;
+ state.distbits = opts.bits;
+ // state.distcode = state.next;
+
+ if (ret) {
+ strm.msg = 'invalid distances set';
+ state.mode = BAD;
+ break;
+ }
+ //Tracev((stderr, 'inflate: codes ok\n'));
+ state.mode = LEN_;
+ if (flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case LEN_:
+ state.mode = LEN;
+ /* falls through */
+ case LEN:
+ if (have >= 6 && left >= 258) {
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+ inflate_fast(strm, _out);
+ //--- LOAD() ---
+ put = strm.next_out;
+ output = strm.output;
+ left = strm.avail_out;
+ next = strm.next_in;
+ input = strm.input;
+ have = strm.avail_in;
+ hold = state.hold;
+ bits = state.bits;
+ //---
+
+ if (state.mode === TYPE) {
+ state.back = -1;
+ }
+ break;
+ }
+ state.back = 0;
+ for (;;) {
+ here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if (here_bits <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if (here_op && (here_op & 0xf0) === 0) {
+ last_bits = here_bits;
+ last_op = here_op;
+ last_val = here_val;
+ for (;;) {
+ here = state.lencode[last_val +
+ ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((last_bits + here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ //--- DROPBITS(last.bits) ---//
+ hold >>>= last_bits;
+ bits -= last_bits;
+ //---//
+ state.back += last_bits;
+ }
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.back += here_bits;
+ state.length = here_val;
+ if (here_op === 0) {
+ //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ // "inflate: literal '%c'\n" :
+ // "inflate: literal 0x%02x\n", here.val));
+ state.mode = LIT;
+ break;
+ }
+ if (here_op & 32) {
+ //Tracevv((stderr, "inflate: end of block\n"));
+ state.back = -1;
+ state.mode = TYPE;
+ break;
+ }
+ if (here_op & 64) {
+ strm.msg = 'invalid literal/length code';
+ state.mode = BAD;
+ break;
+ }
+ state.extra = here_op & 15;
+ state.mode = LENEXT;
+ /* falls through */
+ case LENEXT:
+ if (state.extra) {
+ //=== NEEDBITS(state.extra);
+ n = state.extra;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+ //--- DROPBITS(state.extra) ---//
+ hold >>>= state.extra;
+ bits -= state.extra;
+ //---//
+ state.back += state.extra;
+ }
+ //Tracevv((stderr, "inflate: length %u\n", state.length));
+ state.was = state.length;
+ state.mode = DIST;
+ /* falls through */
+ case DIST:
+ for (;;) {
+ here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if ((here_op & 0xf0) === 0) {
+ last_bits = here_bits;
+ last_op = here_op;
+ last_val = here_val;
+ for (;;) {
+ here = state.distcode[last_val +
+ ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((last_bits + here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ //--- DROPBITS(last.bits) ---//
+ hold >>>= last_bits;
+ bits -= last_bits;
+ //---//
+ state.back += last_bits;
+ }
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.back += here_bits;
+ if (here_op & 64) {
+ strm.msg = 'invalid distance code';
+ state.mode = BAD;
+ break;
+ }
+ state.offset = here_val;
+ state.extra = (here_op) & 15;
+ state.mode = DISTEXT;
+ /* falls through */
+ case DISTEXT:
+ if (state.extra) {
+ //=== NEEDBITS(state.extra);
+ n = state.extra;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+ //--- DROPBITS(state.extra) ---//
+ hold >>>= state.extra;
+ bits -= state.extra;
+ //---//
+ state.back += state.extra;
+ }
+//#ifdef INFLATE_STRICT
+ if (state.offset > state.dmax) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break;
+ }
+//#endif
+ //Tracevv((stderr, "inflate: distance %u\n", state.offset));
+ state.mode = MATCH;
+ /* falls through */
+ case MATCH:
+ if (left === 0) { break inf_leave; }
+ copy = _out - left;
+ if (state.offset > copy) { /* copy from window */
+ copy = state.offset - copy;
+ if (copy > state.whave) {
+ if (state.sane) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break;
+ }
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+// Trace((stderr, "inflate.c too far\n"));
+// copy -= state.whave;
+// if (copy > state.length) { copy = state.length; }
+// if (copy > left) { copy = left; }
+// left -= copy;
+// state.length -= copy;
+// do {
+// output[put++] = 0;
+// } while (--copy);
+// if (state.length === 0) { state.mode = LEN; }
+// break;
+//#endif
+ }
+ if (copy > state.wnext) {
+ copy -= state.wnext;
+ from = state.wsize - copy;
+ }
+ else {
+ from = state.wnext - copy;
+ }
+ if (copy > state.length) { copy = state.length; }
+ from_source = state.window;
+ }
+ else { /* copy from output */
+ from_source = output;
+ from = put - state.offset;
+ copy = state.length;
+ }
+ if (copy > left) { copy = left; }
+ left -= copy;
+ state.length -= copy;
+ do {
+ output[put++] = from_source[from++];
+ } while (--copy);
+ if (state.length === 0) { state.mode = LEN; }
+ break;
+ case LIT:
+ if (left === 0) { break inf_leave; }
+ output[put++] = state.length;
+ left--;
+ state.mode = LEN;
+ break;
+ case CHECK:
+ if (state.wrap) {
+ //=== NEEDBITS(32);
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ // Use '|' insdead of '+' to make sure that result is signed
+ hold |= input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ _out -= left;
+ strm.total_out += _out;
+ state.total += _out;
+ if (_out) {
+ strm.adler = state.check =
+ /*UPDATE(state.check, put - _out, _out);*/
+ (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));
+
+ }
+ _out = left;
+ // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too
+ if ((state.flags ? hold : zswap32(hold)) !== state.check) {
+ strm.msg = 'incorrect data check';
+ state.mode = BAD;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ //Tracev((stderr, "inflate: check matches trailer\n"));
+ }
+ state.mode = LENGTH;
+ /* falls through */
+ case LENGTH:
+ if (state.wrap && state.flags) {
+ //=== NEEDBITS(32);
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (hold !== (state.total & 0xffffffff)) {
+ strm.msg = 'incorrect length check';
+ state.mode = BAD;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ //Tracev((stderr, "inflate: length matches trailer\n"));
+ }
+ state.mode = DONE;
+ /* falls through */
+ case DONE:
+ ret = Z_STREAM_END;
+ break inf_leave;
+ case BAD:
+ ret = Z_DATA_ERROR;
+ break inf_leave;
+ case MEM:
+ return Z_MEM_ERROR;
+ case SYNC:
+ /* falls through */
+ default:
+ return Z_STREAM_ERROR;
+ }
+ }
+
+ // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"
+
+ /*
+ Return from inflate(), updating the total counts and the check value.
+ If there was no progress during the inflate() call, return a buffer
+ error. Call updatewindow() to create and/or update the window state.
+ Note: a memory error from inflate() is non-recoverable.
+ */
+
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+
+ if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
+ (state.mode < CHECK || flush !== Z_FINISH))) {
+ if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
+ state.mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ }
+ _in -= strm.avail_in;
+ _out -= strm.avail_out;
+ strm.total_in += _in;
+ strm.total_out += _out;
+ state.total += _out;
+ if (state.wrap && _out) {
+ strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
+ (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));
+ }
+ strm.data_type = state.bits + (state.last ? 64 : 0) +
+ (state.mode === TYPE ? 128 : 0) +
+ (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
+ if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {
+ ret = Z_BUF_ERROR;
+ }
+ return ret;
+}
+
+function inflateEnd(strm) {
+
+ if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
+ return Z_STREAM_ERROR;
+ }
+
+ var state = strm.state;
+ if (state.window) {
+ state.window = null;
+ }
+ strm.state = null;
+ return Z_OK;
+}
+
+function inflateGetHeader(strm, head) {
+ var state;
+
+ /* check state */
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+ if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }
+
+ /* save header structure */
+ state.head = head;
+ head.done = false;
+ return Z_OK;
+}
+
+function inflateSetDictionary(strm, dictionary) {
+ var dictLength = dictionary.length;
+
+ var state;
+ var dictid;
+ var ret;
+
+ /* check state */
+ if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; }
+ state = strm.state;
+
+ if (state.wrap !== 0 && state.mode !== DICT) {
+ return Z_STREAM_ERROR;
+ }
+
+ /* check for correct dictionary identifier */
+ if (state.mode === DICT) {
+ dictid = 1; /* adler32(0, null, 0)*/
+ /* dictid = adler32(dictid, dictionary, dictLength); */
+ dictid = adler32(dictid, dictionary, dictLength, 0);
+ if (dictid !== state.check) {
+ return Z_DATA_ERROR;
+ }
+ }
+ /* copy dictionary to window using updatewindow(), which will amend the
+ existing dictionary if appropriate */
+ ret = updatewindow(strm, dictionary, dictLength, dictLength);
+ if (ret) {
+ state.mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ state.havedict = 1;
+ // Tracev((stderr, "inflate: dictionary set\n"));
+ return Z_OK;
+}
+
+export { inflateReset, inflateReset2, inflateResetKeep, inflateInit, inflateInit2, inflate, inflateEnd, inflateGetHeader, inflateSetDictionary };
+export var inflateInfo = 'pako inflate (from Nodeca project)';
+
+/* Not implemented
+exports.inflateCopy = inflateCopy;
+exports.inflateGetDictionary = inflateGetDictionary;
+exports.inflateMark = inflateMark;
+exports.inflatePrime = inflatePrime;
+exports.inflateSync = inflateSync;
+exports.inflateSyncPoint = inflateSyncPoint;
+exports.inflateUndermine = inflateUndermine;
+*/
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/inftrees.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inftrees.js
new file mode 100644
index 0000000..78b7c9e
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inftrees.js
@@ -0,0 +1,322 @@
+import * as utils from "../utils/common.js";
+
+var MAXBITS = 15;
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+var lbase = [ /* Length codes 257..285 base */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
+];
+
+var lext = [ /* Length codes 257..285 extra */
+ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+ 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
+];
+
+var dbase = [ /* Distance codes 0..29 base */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577, 0, 0
+];
+
+var dext = [ /* Distance codes 0..29 extra */
+ 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+ 23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+ 28, 28, 29, 29, 64, 64
+];
+
+export default function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
+{
+ var bits = opts.bits;
+ //here = opts.here; /* table entry for duplication */
+
+ var len = 0; /* a code's length in bits */
+ var sym = 0; /* index of code symbols */
+ var min = 0, max = 0; /* minimum and maximum code lengths */
+ var root = 0; /* number of index bits for root table */
+ var curr = 0; /* number of index bits for current table */
+ var drop = 0; /* code bits to drop for sub-table */
+ var left = 0; /* number of prefix codes available */
+ var used = 0; /* code entries in table used */
+ var huff = 0; /* Huffman code */
+ var incr; /* for incrementing code, index */
+ var fill; /* index for replicating entries */
+ var low; /* low bits for current root entry */
+ var mask; /* mask for low root bits */
+ var next; /* next available space in table */
+ var base = null; /* base value table to use */
+ var base_index = 0;
+// var shoextra; /* extra bits table to use */
+ var end; /* use base and extra for symbol > end */
+ var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */
+ var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */
+ var extra = null;
+ var extra_index = 0;
+
+ var here_bits, here_op, here_val;
+
+ /*
+ Process a set of code lengths to create a canonical Huffman code. The
+ code lengths are lens[0..codes-1]. Each length corresponds to the
+ symbols 0..codes-1. The Huffman code is generated by first sorting the
+ symbols by length from short to long, and retaining the symbol order
+ for codes with equal lengths. Then the code starts with all zero bits
+ for the first code of the shortest length, and the codes are integer
+ increments for the same length, and zeros are appended as the length
+ increases. For the deflate format, these bits are stored backwards
+ from their more natural integer increment ordering, and so when the
+ decoding tables are built in the large loop below, the integer codes
+ are incremented backwards.
+
+ This routine assumes, but does not check, that all of the entries in
+ lens[] are in the range 0..MAXBITS. The caller must assure this.
+ 1..MAXBITS is interpreted as that code length. zero means that that
+ symbol does not occur in this code.
+
+ The codes are sorted by computing a count of codes for each length,
+ creating from that a table of starting indices for each length in the
+ sorted table, and then entering the symbols in order in the sorted
+ table. The sorted table is work[], with that space being provided by
+ the caller.
+
+ The length counts are used for other purposes as well, i.e. finding
+ the minimum and maximum length codes, determining if there are any
+ codes at all, checking for a valid set of lengths, and looking ahead
+ at length counts to determine sub-table sizes when building the
+ decoding tables.
+ */
+
+ /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+ for (len = 0; len <= MAXBITS; len++) {
+ count[len] = 0;
+ }
+ for (sym = 0; sym < codes; sym++) {
+ count[lens[lens_index + sym]]++;
+ }
+
+ /* bound code lengths, force root to be within code lengths */
+ root = bits;
+ for (max = MAXBITS; max >= 1; max--) {
+ if (count[max] !== 0) { break; }
+ }
+ if (root > max) {
+ root = max;
+ }
+ if (max === 0) { /* no symbols to code at all */
+ //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */
+ //table.bits[opts.table_index] = 1; //here.bits = (var char)1;
+ //table.val[opts.table_index++] = 0; //here.val = (var short)0;
+ table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+
+ //table.op[opts.table_index] = 64;
+ //table.bits[opts.table_index] = 1;
+ //table.val[opts.table_index++] = 0;
+ table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+ opts.bits = 1;
+ return 0; /* no symbols, but wait for decoding to report error */
+ }
+ for (min = 1; min < max; min++) {
+ if (count[min] !== 0) { break; }
+ }
+ if (root < min) {
+ root = min;
+ }
+
+ /* check for an over-subscribed or incomplete set of lengths */
+ left = 1;
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1;
+ left -= count[len];
+ if (left < 0) {
+ return -1;
+ } /* over-subscribed */
+ }
+ if (left > 0 && (type === CODES || max !== 1)) {
+ return -1; /* incomplete set */
+ }
+
+ /* generate offsets into symbol table for each length for sorting */
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++) {
+ offs[len + 1] = offs[len] + count[len];
+ }
+
+ /* sort symbols by length, by symbol order within each length */
+ for (sym = 0; sym < codes; sym++) {
+ if (lens[lens_index + sym] !== 0) {
+ work[offs[lens[lens_index + sym]]++] = sym;
+ }
+ }
+
+ /*
+ Create and fill in decoding tables. In this loop, the table being
+ filled is at next and has curr index bits. The code being used is huff
+ with length len. That code is converted to an index by dropping drop
+ bits off of the bottom. For codes where len is less than drop + curr,
+ those top drop + curr - len bits are incremented through all values to
+ fill the table with replicated entries.
+
+ root is the number of index bits for the root table. When len exceeds
+ root, sub-tables are created pointed to by the root entry with an index
+ of the low root bits of huff. This is saved in low to check for when a
+ new sub-table should be started. drop is zero when the root table is
+ being filled, and drop is root when sub-tables are being filled.
+
+ When a new sub-table is needed, it is necessary to look ahead in the
+ code lengths to determine what size sub-table is needed. The length
+ counts are used for this, and so count[] is decremented as codes are
+ entered in the tables.
+
+ used keeps track of how many table entries have been allocated from the
+ provided *table space. It is checked for LENS and DIST tables against
+ the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+ the initial root table size constants. See the comments in inftrees.h
+ for more information.
+
+ sym increments through all symbols, and the loop terminates when
+ all codes of length max, i.e. all codes, have been processed. This
+ routine permits incomplete codes, so another loop after this one fills
+ in the rest of the decoding tables with invalid code markers.
+ */
+
+ /* set up for code type */
+ // poor man optimization - use if-else instead of switch,
+ // to avoid deopts in old v8
+ if (type === CODES) {
+ base = extra = work; /* dummy value--not used */
+ end = 19;
+
+ } else if (type === LENS) {
+ base = lbase;
+ base_index -= 257;
+ extra = lext;
+ extra_index -= 257;
+ end = 256;
+
+ } else { /* DISTS */
+ base = dbase;
+ extra = dext;
+ end = -1;
+ }
+
+ /* initialize opts for loop */
+ huff = 0; /* starting code */
+ sym = 0; /* starting code symbol */
+ len = min; /* starting code length */
+ next = table_index; /* current table to fill in */
+ curr = root; /* current table index bits */
+ drop = 0; /* current bits to drop from code for index */
+ low = -1; /* trigger new sub-table when len > root */
+ used = 1 << root; /* use root table entries */
+ mask = used - 1; /* mask for comparing low */
+
+ /* check available table space */
+ if ((type === LENS && used > ENOUGH_LENS) ||
+ (type === DISTS && used > ENOUGH_DISTS)) {
+ return 1;
+ }
+
+ /* process all codes and make table entries */
+ for (;;) {
+ /* create table entry */
+ here_bits = len - drop;
+ if (work[sym] < end) {
+ here_op = 0;
+ here_val = work[sym];
+ }
+ else if (work[sym] > end) {
+ here_op = extra[extra_index + work[sym]];
+ here_val = base[base_index + work[sym]];
+ }
+ else {
+ here_op = 32 + 64; /* end of block */
+ here_val = 0;
+ }
+
+ /* replicate for those indices with low len bits equal to huff */
+ incr = 1 << (len - drop);
+ fill = 1 << curr;
+ min = fill; /* save offset to next table */
+ do {
+ fill -= incr;
+ table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
+ } while (fill !== 0);
+
+ /* backwards increment the len-bit code huff */
+ incr = 1 << (len - 1);
+ while (huff & incr) {
+ incr >>= 1;
+ }
+ if (incr !== 0) {
+ huff &= incr - 1;
+ huff += incr;
+ } else {
+ huff = 0;
+ }
+
+ /* go to next symbol, update count, len */
+ sym++;
+ if (--count[len] === 0) {
+ if (len === max) { break; }
+ len = lens[lens_index + work[sym]];
+ }
+
+ /* create new sub-table if needed */
+ if (len > root && (huff & mask) !== low) {
+ /* if first time, transition to sub-tables */
+ if (drop === 0) {
+ drop = root;
+ }
+
+ /* increment past last table */
+ next += min; /* here min is 1 << curr */
+
+ /* determine length of next table */
+ curr = len - drop;
+ left = 1 << curr;
+ while (curr + drop < max) {
+ left -= count[curr + drop];
+ if (left <= 0) { break; }
+ curr++;
+ left <<= 1;
+ }
+
+ /* check for enough space */
+ used += 1 << curr;
+ if ((type === LENS && used > ENOUGH_LENS) ||
+ (type === DISTS && used > ENOUGH_DISTS)) {
+ return 1;
+ }
+
+ /* point entry in root table to sub-table */
+ low = huff & mask;
+ /*table.op[low] = curr;
+ table.bits[low] = root;
+ table.val[low] = next - opts.table_index;*/
+ table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
+ }
+ }
+
+ /* fill in remaining table entry if code is incomplete (guaranteed to have
+ at most one remaining entry, since if the code is incomplete, the
+ maximum code length that was allowed to get this far is one bit) */
+ if (huff !== 0) {
+ //table.op[next + huff] = 64; /* invalid code marker */
+ //table.bits[next + huff] = len - drop;
+ //table.val[next + huff] = 0;
+ table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
+ }
+
+ /* set return parameters */
+ //opts.table_index += used;
+ opts.bits = root;
+ return 0;
+};
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/messages.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/messages.js
new file mode 100644
index 0000000..f95cb70
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/messages.js
@@ -0,0 +1,11 @@
+export default {
+ 2: 'need dictionary', /* Z_NEED_DICT 2 */
+ 1: 'stream end', /* Z_STREAM_END 1 */
+ 0: '', /* Z_OK 0 */
+ '-1': 'file error', /* Z_ERRNO (-1) */
+ '-2': 'stream error', /* Z_STREAM_ERROR (-2) */
+ '-3': 'data error', /* Z_DATA_ERROR (-3) */
+ '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */
+ '-5': 'buffer error', /* Z_BUF_ERROR (-5) */
+ '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */
+};
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/trees.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/trees.js
new file mode 100644
index 0000000..a69b8a5
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/trees.js
@@ -0,0 +1,1195 @@
+import * as utils from "../utils/common.js";
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+//var Z_FILTERED = 1;
+//var Z_HUFFMAN_ONLY = 2;
+//var Z_RLE = 3;
+var Z_FIXED = 4;
+//var Z_DEFAULT_STRATEGY = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+var Z_BINARY = 0;
+var Z_TEXT = 1;
+//var Z_ASCII = 1; // = Z_TEXT
+var Z_UNKNOWN = 2;
+
+/*============================================================================*/
+
+
+function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+// From zutil.h
+
+var STORED_BLOCK = 0;
+var STATIC_TREES = 1;
+var DYN_TREES = 2;
+/* The three kinds of block type */
+
+var MIN_MATCH = 3;
+var MAX_MATCH = 258;
+/* The minimum and maximum match lengths */
+
+// From deflate.h
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+var LENGTH_CODES = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+
+var LITERALS = 256;
+/* number of literal bytes 0..255 */
+
+var L_CODES = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+var D_CODES = 30;
+/* number of distance codes */
+
+var BL_CODES = 19;
+/* number of codes used to transfer the bit lengths */
+
+var HEAP_SIZE = 2 * L_CODES + 1;
+/* maximum heap size */
+
+var MAX_BITS = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+var Buf_size = 16;
+/* size of bit buffer in bi_buf */
+
+
+/* ===========================================================================
+ * Constants
+ */
+
+var MAX_BL_BITS = 7;
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+var END_BLOCK = 256;
+/* end of block literal code */
+
+var REP_3_6 = 16;
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+var REPZ_3_10 = 17;
+/* repeat a zero length 3-10 times (3 bits of repeat count) */
+
+var REPZ_11_138 = 18;
+/* repeat a zero length 11-138 times (7 bits of repeat count) */
+
+/* eslint-disable comma-spacing,array-bracket-spacing */
+var extra_lbits = /* extra bits for each length code */
+ [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
+
+var extra_dbits = /* extra bits for each distance code */
+ [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
+
+var extra_blbits = /* extra bits for each bit length code */
+ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7];
+
+var bl_order =
+ [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];
+/* eslint-enable comma-spacing,array-bracket-spacing */
+
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+// We pre-fill arrays with 0 to avoid uninitialized gaps
+
+var DIST_CODE_LEN = 512; /* see definition of array dist_code below */
+
+// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1
+var static_ltree = new Array((L_CODES + 2) * 2);
+zero(static_ltree);
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+var static_dtree = new Array(D_CODES * 2);
+zero(static_dtree);
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+var _dist_code = new Array(DIST_CODE_LEN);
+zero(_dist_code);
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+var _length_code = new Array(MAX_MATCH - MIN_MATCH + 1);
+zero(_length_code);
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+var base_length = new Array(LENGTH_CODES);
+zero(base_length);
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+var base_dist = new Array(D_CODES);
+zero(base_dist);
+/* First normalized distance for each code (0 = distance of 1) */
+
+
+function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {
+
+ this.static_tree = static_tree; /* static tree or NULL */
+ this.extra_bits = extra_bits; /* extra bits for each code or NULL */
+ this.extra_base = extra_base; /* base index for extra_bits */
+ this.elems = elems; /* max number of elements in the tree */
+ this.max_length = max_length; /* max bit length for the codes */
+
+ // show if `static_tree` has data or dummy - needed for monomorphic objects
+ this.has_stree = static_tree && static_tree.length;
+}
+
+
+var static_l_desc;
+var static_d_desc;
+var static_bl_desc;
+
+
+function TreeDesc(dyn_tree, stat_desc) {
+ this.dyn_tree = dyn_tree; /* the dynamic tree */
+ this.max_code = 0; /* largest code with non zero frequency */
+ this.stat_desc = stat_desc; /* the corresponding static tree */
+}
+
+
+
+function d_code(dist) {
+ return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
+}
+
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+function put_short(s, w) {
+// put_byte(s, (uch)((w) & 0xff));
+// put_byte(s, (uch)((ush)(w) >> 8));
+ s.pending_buf[s.pending++] = (w) & 0xff;
+ s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
+}
+
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+function send_bits(s, value, length) {
+ if (s.bi_valid > (Buf_size - length)) {
+ s.bi_buf |= (value << s.bi_valid) & 0xffff;
+ put_short(s, s.bi_buf);
+ s.bi_buf = value >> (Buf_size - s.bi_valid);
+ s.bi_valid += length - Buf_size;
+ } else {
+ s.bi_buf |= (value << s.bi_valid) & 0xffff;
+ s.bi_valid += length;
+ }
+}
+
+
+function send_code(s, c, tree) {
+ send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
+}
+
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+function bi_reverse(code, len) {
+ var res = 0;
+ do {
+ res |= code & 1;
+ code >>>= 1;
+ res <<= 1;
+ } while (--len > 0);
+ return res >>> 1;
+}
+
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+function bi_flush(s) {
+ if (s.bi_valid === 16) {
+ put_short(s, s.bi_buf);
+ s.bi_buf = 0;
+ s.bi_valid = 0;
+
+ } else if (s.bi_valid >= 8) {
+ s.pending_buf[s.pending++] = s.bi_buf & 0xff;
+ s.bi_buf >>= 8;
+ s.bi_valid -= 8;
+ }
+}
+
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ * above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ * array bl_count contains the frequencies for each bit length.
+ * The length opt_len is updated; static_len is also updated if stree is
+ * not null.
+ */
+function gen_bitlen(s, desc)
+// deflate_state *s;
+// tree_desc *desc; /* the tree descriptor */
+{
+ var tree = desc.dyn_tree;
+ var max_code = desc.max_code;
+ var stree = desc.stat_desc.static_tree;
+ var has_stree = desc.stat_desc.has_stree;
+ var extra = desc.stat_desc.extra_bits;
+ var base = desc.stat_desc.extra_base;
+ var max_length = desc.stat_desc.max_length;
+ var h; /* heap index */
+ var n, m; /* iterate over the tree elements */
+ var bits; /* bit length */
+ var xbits; /* extra bits */
+ var f; /* frequency */
+ var overflow = 0; /* number of elements with bit length too large */
+
+ for (bits = 0; bits <= MAX_BITS; bits++) {
+ s.bl_count[bits] = 0;
+ }
+
+ /* In a first pass, compute the optimal bit lengths (which may
+ * overflow in the case of the bit length tree).
+ */
+ tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */
+
+ for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+ n = s.heap[h];
+ bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
+ if (bits > max_length) {
+ bits = max_length;
+ overflow++;
+ }
+ tree[n * 2 + 1]/*.Len*/ = bits;
+ /* We overwrite tree[n].Dad which is no longer needed */
+
+ if (n > max_code) { continue; } /* not a leaf node */
+
+ s.bl_count[bits]++;
+ xbits = 0;
+ if (n >= base) {
+ xbits = extra[n - base];
+ }
+ f = tree[n * 2]/*.Freq*/;
+ s.opt_len += f * (bits + xbits);
+ if (has_stree) {
+ s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
+ }
+ }
+ if (overflow === 0) { return; }
+
+ // Trace((stderr,"\nbit length overflow\n"));
+ /* This happens for example on obj2 and pic of the Calgary corpus */
+
+ /* Find the first bit length which could increase: */
+ do {
+ bits = max_length - 1;
+ while (s.bl_count[bits] === 0) { bits--; }
+ s.bl_count[bits]--; /* move one leaf down the tree */
+ s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
+ s.bl_count[max_length]--;
+ /* The brother of the overflow item also moves one step up,
+ * but this does not affect bl_count[max_length]
+ */
+ overflow -= 2;
+ } while (overflow > 0);
+
+ /* Now recompute all bit lengths, scanning in increasing frequency.
+ * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+ * lengths instead of fixing only the wrong ones. This idea is taken
+ * from 'ar' written by Haruhiko Okumura.)
+ */
+ for (bits = max_length; bits !== 0; bits--) {
+ n = s.bl_count[bits];
+ while (n !== 0) {
+ m = s.heap[--h];
+ if (m > max_code) { continue; }
+ if (tree[m * 2 + 1]/*.Len*/ !== bits) {
+ // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+ s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
+ tree[m * 2 + 1]/*.Len*/ = bits;
+ }
+ n--;
+ }
+ }
+}
+
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ * zero code length.
+ */
+function gen_codes(tree, max_code, bl_count)
+// ct_data *tree; /* the tree to decorate */
+// int max_code; /* largest code with non zero frequency */
+// ushf *bl_count; /* number of codes at each bit length */
+{
+ var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
+ var code = 0; /* running code value */
+ var bits; /* bit index */
+ var n; /* code index */
+
+ /* The distribution counts are first used to generate the code values
+ * without bit reversal.
+ */
+ for (bits = 1; bits <= MAX_BITS; bits++) {
+ next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
+ }
+ /* Check that the bit counts in bl_count are consistent. The last code
+ * must be all ones.
+ */
+ //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+ // "inconsistent bit counts");
+ //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+ for (n = 0; n <= max_code; n++) {
+ var len = tree[n * 2 + 1]/*.Len*/;
+ if (len === 0) { continue; }
+ /* Now reverse the bits */
+ tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);
+
+ //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+ // n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+ }
+}
+
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+function tr_static_init() {
+ var n; /* iterates over tree elements */
+ var bits; /* bit counter */
+ var length; /* length value */
+ var code; /* code value */
+ var dist; /* distance index */
+ var bl_count = new Array(MAX_BITS + 1);
+ /* number of codes at each bit length for an optimal tree */
+
+ // do check in _tr_init()
+ //if (static_init_done) return;
+
+ /* For some embedded targets, global variables are not initialized: */
+/*#ifdef NO_INIT_GLOBAL_POINTERS
+ static_l_desc.static_tree = static_ltree;
+ static_l_desc.extra_bits = extra_lbits;
+ static_d_desc.static_tree = static_dtree;
+ static_d_desc.extra_bits = extra_dbits;
+ static_bl_desc.extra_bits = extra_blbits;
+#endif*/
+
+ /* Initialize the mapping length (0..255) -> length code (0..28) */
+ length = 0;
+ for (code = 0; code < LENGTH_CODES - 1; code++) {
+ base_length[code] = length;
+ for (n = 0; n < (1 << extra_lbits[code]); n++) {
+ _length_code[length++] = code;
+ }
+ }
+ //Assert (length == 256, "tr_static_init: length != 256");
+ /* Note that the length 255 (match length 258) can be represented
+ * in two different ways: code 284 + 5 bits or code 285, so we
+ * overwrite length_code[255] to use the best encoding:
+ */
+ _length_code[length - 1] = code;
+
+ /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+ dist = 0;
+ for (code = 0; code < 16; code++) {
+ base_dist[code] = dist;
+ for (n = 0; n < (1 << extra_dbits[code]); n++) {
+ _dist_code[dist++] = code;
+ }
+ }
+ //Assert (dist == 256, "tr_static_init: dist != 256");
+ dist >>= 7; /* from now on, all distances are divided by 128 */
+ for (; code < D_CODES; code++) {
+ base_dist[code] = dist << 7;
+ for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
+ _dist_code[256 + dist++] = code;
+ }
+ }
+ //Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+ /* Construct the codes of the static literal tree */
+ for (bits = 0; bits <= MAX_BITS; bits++) {
+ bl_count[bits] = 0;
+ }
+
+ n = 0;
+ while (n <= 143) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 8;
+ n++;
+ bl_count[8]++;
+ }
+ while (n <= 255) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 9;
+ n++;
+ bl_count[9]++;
+ }
+ while (n <= 279) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 7;
+ n++;
+ bl_count[7]++;
+ }
+ while (n <= 287) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 8;
+ n++;
+ bl_count[8]++;
+ }
+ /* Codes 286 and 287 do not exist, but we must include them in the
+ * tree construction to get a canonical Huffman tree (longest code
+ * all ones)
+ */
+ gen_codes(static_ltree, L_CODES + 1, bl_count);
+
+ /* The static distance tree is trivial: */
+ for (n = 0; n < D_CODES; n++) {
+ static_dtree[n * 2 + 1]/*.Len*/ = 5;
+ static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
+ }
+
+ // Now data ready and we can init static trees
+ static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
+ static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS);
+ static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS);
+
+ //static_init_done = true;
+}
+
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+function init_block(s) {
+ var n; /* iterates over tree elements */
+
+ /* Initialize the trees. */
+ for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
+ for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
+ for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }
+
+ s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
+ s.opt_len = s.static_len = 0;
+ s.last_lit = s.matches = 0;
+}
+
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+function bi_windup(s)
+{
+ if (s.bi_valid > 8) {
+ put_short(s, s.bi_buf);
+ } else if (s.bi_valid > 0) {
+ //put_byte(s, (Byte)s->bi_buf);
+ s.pending_buf[s.pending++] = s.bi_buf;
+ }
+ s.bi_buf = 0;
+ s.bi_valid = 0;
+}
+
+/* ===========================================================================
+ * Copy a stored block, storing first the length and its
+ * one's complement if requested.
+ */
+function copy_block(s, buf, len, header)
+//DeflateState *s;
+//charf *buf; /* the input data */
+//unsigned len; /* its length */
+//int header; /* true if block header must be written */
+{
+ bi_windup(s); /* align on byte boundary */
+
+ if (header) {
+ put_short(s, len);
+ put_short(s, ~len);
+ }
+// while (len--) {
+// put_byte(s, *buf++);
+// }
+ utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);
+ s.pending += len;
+}
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+function smaller(tree, n, m, depth) {
+ var _n2 = n * 2;
+ var _m2 = m * 2;
+ return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
+ (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
+}
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+function pqdownheap(s, tree, k)
+// deflate_state *s;
+// ct_data *tree; /* the tree to restore */
+// int k; /* node to move down */
+{
+ var v = s.heap[k];
+ var j = k << 1; /* left son of k */
+ while (j <= s.heap_len) {
+ /* Set j to the smallest of the two sons: */
+ if (j < s.heap_len &&
+ smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
+ j++;
+ }
+ /* Exit if v is smaller than both sons */
+ if (smaller(tree, v, s.heap[j], s.depth)) { break; }
+
+ /* Exchange v with the smallest son */
+ s.heap[k] = s.heap[j];
+ k = j;
+
+ /* And continue down the tree, setting j to the left son of k */
+ j <<= 1;
+ }
+ s.heap[k] = v;
+}
+
+
+// inlined manually
+// var SMALLEST = 1;
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+function compress_block(s, ltree, dtree)
+// deflate_state *s;
+// const ct_data *ltree; /* literal tree */
+// const ct_data *dtree; /* distance tree */
+{
+ var dist; /* distance of matched string */
+ var lc; /* match length or unmatched char (if dist == 0) */
+ var lx = 0; /* running index in l_buf */
+ var code; /* the code to send */
+ var extra; /* number of extra bits to send */
+
+ if (s.last_lit !== 0) {
+ do {
+ dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
+ lc = s.pending_buf[s.l_buf + lx];
+ lx++;
+
+ if (dist === 0) {
+ send_code(s, lc, ltree); /* send a literal byte */
+ //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+ } else {
+ /* Here, lc is the match length - MIN_MATCH */
+ code = _length_code[lc];
+ send_code(s, code + LITERALS + 1, ltree); /* send the length code */
+ extra = extra_lbits[code];
+ if (extra !== 0) {
+ lc -= base_length[code];
+ send_bits(s, lc, extra); /* send the extra length bits */
+ }
+ dist--; /* dist is now the match distance - 1 */
+ code = d_code(dist);
+ //Assert (code < D_CODES, "bad d_code");
+
+ send_code(s, code, dtree); /* send the distance code */
+ extra = extra_dbits[code];
+ if (extra !== 0) {
+ dist -= base_dist[code];
+ send_bits(s, dist, extra); /* send the extra distance bits */
+ }
+ } /* literal or match pair ? */
+
+ /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+ //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+ // "pendingBuf overflow");
+
+ } while (lx < s.last_lit);
+ }
+
+ send_code(s, END_BLOCK, ltree);
+}
+
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ * and corresponding code. The length opt_len is updated; static_len is
+ * also updated if stree is not null. The field max_code is set.
+ */
+function build_tree(s, desc)
+// deflate_state *s;
+// tree_desc *desc; /* the tree descriptor */
+{
+ var tree = desc.dyn_tree;
+ var stree = desc.stat_desc.static_tree;
+ var has_stree = desc.stat_desc.has_stree;
+ var elems = desc.stat_desc.elems;
+ var n, m; /* iterate over heap elements */
+ var max_code = -1; /* largest code with non zero frequency */
+ var node; /* new node being created */
+
+ /* Construct the initial heap, with least frequent element in
+ * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+ * heap[0] is not used.
+ */
+ s.heap_len = 0;
+ s.heap_max = HEAP_SIZE;
+
+ for (n = 0; n < elems; n++) {
+ if (tree[n * 2]/*.Freq*/ !== 0) {
+ s.heap[++s.heap_len] = max_code = n;
+ s.depth[n] = 0;
+
+ } else {
+ tree[n * 2 + 1]/*.Len*/ = 0;
+ }
+ }
+
+ /* The pkzip format requires that at least one distance code exists,
+ * and that at least one bit should be sent even if there is only one
+ * possible code. So to avoid special checks later on we force at least
+ * two codes of non zero frequency.
+ */
+ while (s.heap_len < 2) {
+ node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
+ tree[node * 2]/*.Freq*/ = 1;
+ s.depth[node] = 0;
+ s.opt_len--;
+
+ if (has_stree) {
+ s.static_len -= stree[node * 2 + 1]/*.Len*/;
+ }
+ /* node is 0 or 1 so it does not have extra bits */
+ }
+ desc.max_code = max_code;
+
+ /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+ * establish sub-heaps of increasing lengths:
+ */
+ for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }
+
+ /* Construct the Huffman tree by repeatedly combining the least two
+ * frequent nodes.
+ */
+ node = elems; /* next internal node of the tree */
+ do {
+ //pqremove(s, tree, n); /* n = node of least frequency */
+ /*** pqremove ***/
+ n = s.heap[1/*SMALLEST*/];
+ s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
+ pqdownheap(s, tree, 1/*SMALLEST*/);
+ /***/
+
+ m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */
+
+ s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
+ s.heap[--s.heap_max] = m;
+
+ /* Create a new node father of n and m */
+ tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
+ s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
+ tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;
+
+ /* and insert the new node in the heap */
+ s.heap[1/*SMALLEST*/] = node++;
+ pqdownheap(s, tree, 1/*SMALLEST*/);
+
+ } while (s.heap_len >= 2);
+
+ s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];
+
+ /* At this point, the fields freq and dad are set. We can now
+ * generate the bit lengths.
+ */
+ gen_bitlen(s, desc);
+
+ /* The field len is now set, we can generate the bit codes */
+ gen_codes(tree, max_code, s.bl_count);
+}
+
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+function scan_tree(s, tree, max_code)
+// deflate_state *s;
+// ct_data *tree; /* the tree to be scanned */
+// int max_code; /* and its largest code of non zero frequency */
+{
+ var n; /* iterates over all tree elements */
+ var prevlen = -1; /* last emitted length */
+ var curlen; /* length of current code */
+
+ var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+ var count = 0; /* repeat count of the current code */
+ var max_count = 7; /* max repeat count */
+ var min_count = 4; /* min repeat count */
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+ }
+ tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen;
+ nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+ if (++count < max_count && curlen === nextlen) {
+ continue;
+
+ } else if (count < min_count) {
+ s.bl_tree[curlen * 2]/*.Freq*/ += count;
+
+ } else if (curlen !== 0) {
+
+ if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
+ s.bl_tree[REP_3_6 * 2]/*.Freq*/++;
+
+ } else if (count <= 10) {
+ s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;
+
+ } else {
+ s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
+ }
+
+ count = 0;
+ prevlen = curlen;
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+
+ } else if (curlen === nextlen) {
+ max_count = 6;
+ min_count = 3;
+
+ } else {
+ max_count = 7;
+ min_count = 4;
+ }
+ }
+}
+
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+function send_tree(s, tree, max_code)
+// deflate_state *s;
+// ct_data *tree; /* the tree to be scanned */
+// int max_code; /* and its largest code of non zero frequency */
+{
+ var n; /* iterates over all tree elements */
+ var prevlen = -1; /* last emitted length */
+ var curlen; /* length of current code */
+
+ var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+ var count = 0; /* repeat count of the current code */
+ var max_count = 7; /* max repeat count */
+ var min_count = 4; /* min repeat count */
+
+ /* tree[max_code+1].Len = -1; */ /* guard already set */
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+ }
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen;
+ nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+ if (++count < max_count && curlen === nextlen) {
+ continue;
+
+ } else if (count < min_count) {
+ do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);
+
+ } else if (curlen !== 0) {
+ if (curlen !== prevlen) {
+ send_code(s, curlen, s.bl_tree);
+ count--;
+ }
+ //Assert(count >= 3 && count <= 6, " 3_6?");
+ send_code(s, REP_3_6, s.bl_tree);
+ send_bits(s, count - 3, 2);
+
+ } else if (count <= 10) {
+ send_code(s, REPZ_3_10, s.bl_tree);
+ send_bits(s, count - 3, 3);
+
+ } else {
+ send_code(s, REPZ_11_138, s.bl_tree);
+ send_bits(s, count - 11, 7);
+ }
+
+ count = 0;
+ prevlen = curlen;
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+
+ } else if (curlen === nextlen) {
+ max_count = 6;
+ min_count = 3;
+
+ } else {
+ max_count = 7;
+ min_count = 4;
+ }
+ }
+}
+
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+function build_bl_tree(s) {
+ var max_blindex; /* index of last bit length code of non zero freq */
+
+ /* Determine the bit length frequencies for literal and distance trees */
+ scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
+ scan_tree(s, s.dyn_dtree, s.d_desc.max_code);
+
+ /* Build the bit length tree: */
+ build_tree(s, s.bl_desc);
+ /* opt_len now includes the length of the tree representations, except
+ * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+ */
+
+ /* Determine the number of bit length codes to send. The pkzip format
+ * requires that at least 4 bit length codes be sent. (appnote.txt says
+ * 3 but the actual value used is 4.)
+ */
+ for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
+ if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
+ break;
+ }
+ }
+ /* Update opt_len to include the bit length tree and counts */
+ s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+ //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+ // s->opt_len, s->static_len));
+
+ return max_blindex;
+}
+
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+function send_all_trees(s, lcodes, dcodes, blcodes)
+// deflate_state *s;
+// int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+ var rank; /* index in bl_order */
+
+ //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+ //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+ // "too many codes");
+ //Tracev((stderr, "\nbl counts: "));
+ send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
+ send_bits(s, dcodes - 1, 5);
+ send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */
+ for (rank = 0; rank < blcodes; rank++) {
+ //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+ send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
+ }
+ //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+ send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
+ //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+ send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
+ //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+}
+
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ * a) There are no non-portable control characters belonging to the
+ * "black list" (0..6, 14..25, 28..31).
+ * b) There is at least one printable character belonging to the
+ * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ * "gray list" that is ignored in this detection algorithm:
+ * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+function detect_data_type(s) {
+ /* black_mask is the bit mask of black-listed bytes
+ * set bits 0..6, 14..25, and 28..31
+ * 0xf3ffc07f = binary 11110011111111111100000001111111
+ */
+ var black_mask = 0xf3ffc07f;
+ var n;
+
+ /* Check for non-textual ("black-listed") bytes. */
+ for (n = 0; n <= 31; n++, black_mask >>>= 1) {
+ if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
+ return Z_BINARY;
+ }
+ }
+
+ /* Check for textual ("white-listed") bytes. */
+ if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
+ s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
+ return Z_TEXT;
+ }
+ for (n = 32; n < LITERALS; n++) {
+ if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
+ return Z_TEXT;
+ }
+ }
+
+ /* There are no "black-listed" or "white-listed" bytes:
+ * this stream either is empty or has tolerated ("gray-listed") bytes only.
+ */
+ return Z_BINARY;
+}
+
+
+var static_init_done = false;
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+function _tr_init(s)
+{
+
+ if (!static_init_done) {
+ tr_static_init();
+ static_init_done = true;
+ }
+
+ s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc);
+ s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc);
+ s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);
+
+ s.bi_buf = 0;
+ s.bi_valid = 0;
+
+ /* Initialize the first block of the first file: */
+ init_block(s);
+}
+
+
+/* ===========================================================================
+ * Send a stored block
+ */
+function _tr_stored_block(s, buf, stored_len, last)
+//DeflateState *s;
+//charf *buf; /* input block */
+//ulg stored_len; /* length of input block */
+//int last; /* one if this is the last block for a file */
+{
+ send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */
+ copy_block(s, buf, stored_len, true); /* with header */
+}
+
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+function _tr_align(s) {
+ send_bits(s, STATIC_TREES << 1, 3);
+ send_code(s, END_BLOCK, static_ltree);
+ bi_flush(s);
+}
+
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+function _tr_flush_block(s, buf, stored_len, last)
+//DeflateState *s;
+//charf *buf; /* input block, or NULL if too old */
+//ulg stored_len; /* length of input block */
+//int last; /* one if this is the last block for a file */
+{
+ var opt_lenb, static_lenb; /* opt_len and static_len in bytes */
+ var max_blindex = 0; /* index of last bit length code of non zero freq */
+
+ /* Build the Huffman trees unless a stored block is forced */
+ if (s.level > 0) {
+
+ /* Check if the file is binary or text */
+ if (s.strm.data_type === Z_UNKNOWN) {
+ s.strm.data_type = detect_data_type(s);
+ }
+
+ /* Construct the literal and distance trees */
+ build_tree(s, s.l_desc);
+ // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+ // s->static_len));
+
+ build_tree(s, s.d_desc);
+ // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+ // s->static_len));
+ /* At this point, opt_len and static_len are the total bit lengths of
+ * the compressed block data, excluding the tree representations.
+ */
+
+ /* Build the bit length tree for the above two trees, and get the index
+ * in bl_order of the last bit length code to send.
+ */
+ max_blindex = build_bl_tree(s);
+
+ /* Determine the best encoding. Compute the block lengths in bytes. */
+ opt_lenb = (s.opt_len + 3 + 7) >>> 3;
+ static_lenb = (s.static_len + 3 + 7) >>> 3;
+
+ // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+ // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+ // s->last_lit));
+
+ if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }
+
+ } else {
+ // Assert(buf != (char*)0, "lost buf");
+ opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+ }
+
+ if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
+ /* 4: two words for the lengths */
+
+ /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+ * Otherwise we can't have processed more than WSIZE input bytes since
+ * the last block flush, because compression would have been
+ * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+ * transform a block into a stored block.
+ */
+ _tr_stored_block(s, buf, stored_len, last);
+
+ } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {
+
+ send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
+ compress_block(s, static_ltree, static_dtree);
+
+ } else {
+ send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
+ send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
+ compress_block(s, s.dyn_ltree, s.dyn_dtree);
+ }
+ // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+ /* The above check is made mod 2^32, for files larger than 512 MB
+ * and uLong implemented on 32 bits.
+ */
+ init_block(s);
+
+ if (last) {
+ bi_windup(s);
+ }
+ // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+ // s->compressed_len-7*last));
+}
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+function _tr_tally(s, dist, lc)
+// deflate_state *s;
+// unsigned dist; /* distance of matched string */
+// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+ //var out_length, in_length, dcode;
+
+ s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff;
+ s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;
+
+ s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
+ s.last_lit++;
+
+ if (dist === 0) {
+ /* lc is the unmatched char */
+ s.dyn_ltree[lc * 2]/*.Freq*/++;
+ } else {
+ s.matches++;
+ /* Here, lc is the match length - MIN_MATCH */
+ dist--; /* dist = match distance - 1 */
+ //Assert((ush)dist < (ush)MAX_DIST(s) &&
+ // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+ // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match");
+
+ s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
+ s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
+ }
+
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+
+//#ifdef TRUNCATE_BLOCK
+// /* Try to guess if it is profitable to stop the current block here */
+// if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
+// /* Compute an upper bound for the compressed length */
+// out_length = s.last_lit*8;
+// in_length = s.strstart - s.block_start;
+//
+// for (dcode = 0; dcode < D_CODES; dcode++) {
+// out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
+// }
+// out_length >>>= 3;
+// //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+// // s->last_lit, in_length, out_length,
+// // 100L - out_length*100L/in_length));
+// if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
+// return true;
+// }
+// }
+//#endif
+
+ return (s.last_lit === s.lit_bufsize - 1);
+ /* We avoid equality with lit_bufsize because of wraparound at 64K
+ * on 16 bit machines and because stored blocks are restricted to
+ * 64K-1 bytes.
+ */
+}
+
+export { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align };
diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/zstream.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/zstream.js
new file mode 100644
index 0000000..e7e674e
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/zstream.js
@@ -0,0 +1,24 @@
+export default function ZStream() {
+ /* next input byte */
+ this.input = null; // JS specific, because we have no pointers
+ this.next_in = 0;
+ /* number of bytes available at input */
+ this.avail_in = 0;
+ /* total number of input bytes read so far */
+ this.total_in = 0;
+ /* next output byte should be put there */
+ this.output = null; // JS specific, because we have no pointers
+ this.next_out = 0;
+ /* remaining free space at output */
+ this.avail_out = 0;
+ /* total number of bytes output so far */
+ this.total_out = 0;
+ /* last error message, NULL if no error */
+ this.msg = ''/*Z_NULL*/;
+ /* not visible by applications */
+ this.state = null;
+ /* best guess about the data type: binary or text */
+ this.data_type = 2/*Z_UNKNOWN*/;
+ /* adler32 value of the uncompressed data */
+ this.adler = 0;
+}
diff --git a/systemvm/agent/noVNC/vendor/promise.js b/systemvm/agent/noVNC/vendor/promise.js
new file mode 100644
index 0000000..6284343
--- /dev/null
+++ b/systemvm/agent/noVNC/vendor/promise.js
@@ -0,0 +1,255 @@
+/* Copyright (c) 2014 Taylor Hakes
+ * Copyright (c) 2014 Forbes Lindesay
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+(function (root) {
+
+ // Store setTimeout reference so promise-polyfill will be unaffected by
+ // other code modifying setTimeout (like sinon.useFakeTimers())
+ var setTimeoutFunc = setTimeout;
+
+ function noop() {}
+
+ // Polyfill for Function.prototype.bind
+ function bind(fn, thisArg) {
+ return function () {
+ fn.apply(thisArg, arguments);
+ };
+ }
+
+ function Promise(fn) {
+ if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
+ if (typeof fn !== 'function') throw new TypeError('not a function');
+ this._state = 0;
+ this._handled = false;
+ this._value = undefined;
+ this._deferreds = [];
+
+ doResolve(fn, this);
+ }
+
+ function handle(self, deferred) {
+ while (self._state === 3) {
+ self = self._value;
+ }
+ if (self._state === 0) {
+ self._deferreds.push(deferred);
+ return;
+ }
+ self._handled = true;
+ Promise._immediateFn(function () {
+ var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
+ if (cb === null) {
+ (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
+ return;
+ }
+ var ret;
+ try {
+ ret = cb(self._value);
+ } catch (e) {
+ reject(deferred.promise, e);
+ return;
+ }
+ resolve(deferred.promise, ret);
+ });
+ }
+
+ function resolve(self, newValue) {
+ try {
+ // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+ if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
+ if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
+ var then = newValue.then;
+ if (newValue instanceof Promise) {
+ self._state = 3;
+ self._value = newValue;
+ finale(self);
+ return;
+ } else if (typeof then === 'function') {
+ doResolve(bind(then, newValue), self);
+ return;
+ }
+ }
+ self._state = 1;
+ self._value = newValue;
+ finale(self);
+ } catch (e) {
+ reject(self, e);
+ }
+ }
+
+ function reject(self, newValue) {
+ self._state = 2;
+ self._value = newValue;
+ finale(self);
+ }
+
+ function finale(self) {
+ if (self._state === 2 && self._deferreds.length === 0) {
+ Promise._immediateFn(function() {
+ if (!self._handled) {
+ Promise._unhandledRejectionFn(self._value);
+ }
+ });
+ }
+
+ for (var i = 0, len = self._deferreds.length; i < len; i++) {
+ handle(self, self._deferreds[i]);
+ }
+ self._deferreds = null;
+ }
+
+ function Handler(onFulfilled, onRejected, promise) {
+ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+ this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+ this.promise = promise;
+ }
+
+ /**
+ * Take a potentially misbehaving resolver function and make sure
+ * onFulfilled and onRejected are only called once.
+ *
+ * Makes no guarantees about asynchrony.
+ */
+ function doResolve(fn, self) {
+ var done = false;
+ try {
+ fn(function (value) {
+ if (done) return;
+ done = true;
+ resolve(self, value);
+ }, function (reason) {
+ if (done) return;
+ done = true;
+ reject(self, reason);
+ });
+ } catch (ex) {
+ if (done) return;
+ done = true;
+ reject(self, ex);
+ }
+ }
+
+ Promise.prototype['catch'] = function (onRejected) {
+ return this.then(null, onRejected);
+ };
+
+ Promise.prototype.then = function (onFulfilled, onRejected) {
+ var prom = new (this.constructor)(noop);
+
+ handle(this, new Handler(onFulfilled, onRejected, prom));
+ return prom;
+ };
+
+ Promise.all = function (arr) {
+ var args = Array.prototype.slice.call(arr);
+
+ return new Promise(function (resolve, reject) {
+ if (args.length === 0) return resolve([]);
+ var remaining = args.length;
+
+ function res(i, val) {
+ try {
+ if (val && (typeof val === 'object' || typeof val === 'function')) {
+ var then = val.then;
+ if (typeof then === 'function') {
+ then.call(val, function (val) {
+ res(i, val);
+ }, reject);
+ return;
+ }
+ }
+ args[i] = val;
+ if (--remaining === 0) {
+ resolve(args);
+ }
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+
+ for (var i = 0; i < args.length; i++) {
+ res(i, args[i]);
+ }
+ });
+ };
+
+ Promise.resolve = function (value) {
+ if (value && typeof value === 'object' && value.constructor === Promise) {
+ return value;
+ }
+
+ return new Promise(function (resolve) {
+ resolve(value);
+ });
+ };
+
+ Promise.reject = function (value) {
+ return new Promise(function (resolve, reject) {
+ reject(value);
+ });
+ };
+
+ Promise.race = function (values) {
+ return new Promise(function (resolve, reject) {
+ for (var i = 0, len = values.length; i < len; i++) {
+ values[i].then(resolve, reject);
+ }
+ });
+ };
+
+ // Use polyfill for setImmediate for performance gains
+ Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
+ function (fn) {
+ setTimeoutFunc(fn, 0);
+ };
+
+ Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
+ if (typeof console !== 'undefined' && console) {
+ console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
+ }
+ };
+
+ /**
+ * Set the immediate function to execute callbacks
+ * @param fn {function} Function to execute
+ * @deprecated
+ */
+ Promise._setImmediateFn = function _setImmediateFn(fn) {
+ Promise._immediateFn = fn;
+ };
+
+ /**
+ * Change the function to execute on unhandled rejection
+ * @param {function} fn Function to execute on unhandled rejection
+ * @deprecated
+ */
+ Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
+ Promise._unhandledRejectionFn = fn;
+ };
+
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = Promise;
+ } else if (!root.Promise) {
+ root.Promise = Promise;
+ }
+
+})(this);
diff --git a/systemvm/agent/noVNC/vnc.html b/systemvm/agent/noVNC/vnc.html
new file mode 100644
index 0000000..212321b
--- /dev/null
+++ b/systemvm/agent/noVNC/vnc.html
@@ -0,0 +1,330 @@
+<!DOCTYPE html>
+<html lang="en" class="noVNC_loading">
+<head>
+
+ <!--
+ noVNC example: simple example using default UI
+ Copyright (C) 2018 The noVNC Authors
+ noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+
+ Connect parameters are provided in query string:
+ http://example.com/?host=HOST&port=PORT&encrypt=1
+ or the fragment:
+ http://example.com/#host=HOST&port=PORT&encrypt=1
+ -->
+ <title>noVNC</title>
+
+ <meta charset="utf-8">
+
+ <!-- Icons (see app/images/icons/Makefile for what the sizes are for) -->
+ <link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
+ <link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/novnc-24x24.png">
+ <link rel="icon" sizes="32x32" type="image/png" href="app/images/icons/novnc-32x32.png">
+ <link rel="icon" sizes="48x48" type="image/png" href="app/images/icons/novnc-48x48.png">
+ <link rel="icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png">
+ <link rel="icon" sizes="64x64" type="image/png" href="app/images/icons/novnc-64x64.png">
+ <link rel="icon" sizes="72x72" type="image/png" href="app/images/icons/novnc-72x72.png">
+ <link rel="icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png">
+ <link rel="icon" sizes="96x96" type="image/png" href="app/images/icons/novnc-96x96.png">
+ <link rel="icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png">
+ <link rel="icon" sizes="144x144" type="image/png" href="app/images/icons/novnc-144x144.png">
+ <link rel="icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png">
+ <link rel="icon" sizes="192x192" type="image/png" href="app/images/icons/novnc-192x192.png">
+ <!-- Firefox currently mishandles SVG, see #1419039
+ <link rel="icon" sizes="any" type="image/svg+xml" href="app/images/icons/novnc-icon.svg">
+ -->
+ <!-- Repeated last so that legacy handling will pick this -->
+ <link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
+
+ <!-- Apple iOS Safari settings -->
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+ <!-- Home Screen Icons (favourites and bookmarks use the normal icons) -->
+ <link rel="apple-touch-icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png">
+ <link rel="apple-touch-icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png">
+ <link rel="apple-touch-icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png">
+ <link rel="apple-touch-icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png">
+
+ <!-- Stylesheets -->
+ <link rel="stylesheet" href="app/styles/base.css">
+
+ <!-- this is included as a normal file in order to catch script-loading errors as well -->
+ <script src="app/error-handler.js"></script>
+
+ <!-- begin scripts -->
+ <!-- promise polyfills promises for IE11 -->
+ <script src="vendor/promise.js"></script>
+ <!-- ES2015/ES6 modules polyfill -->
+ <script type="module">
+ window._noVNC_has_module_support = true;
+ </script>
+ <script>
+ window.addEventListener("load", function() {
+ if (window._noVNC_has_module_support) return;
+ var loader = document.createElement("script");
+ loader.src = "vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
+ document.head.appendChild(loader);
+ });
+ </script>
+ <!-- actual script modules -->
+ <script type="module" crossorigin="anonymous" src="app/ui.js"></script>
+ <!-- end scripts -->
+</head>
+
+<body>
+
+ <div id="noVNC_fallback_error" class="noVNC_center">
+ <div>
+ <div>noVNC encountered an error:</div>
+ <br>
+ <div id="noVNC_fallback_errormsg"></div>
+ </div>
+ </div>
+
+ <!-- noVNC Control Bar -->
+ <div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
+
+ <div id="noVNC_control_bar">
+ <div id="noVNC_control_bar_handle" title="Hide/Show the control bar"><div></div></div>
+
+ <div class="noVNC_scroll">
+
+ <h1 class="noVNC_logo" translate="no"><span>no</span><br>VNC</h1>
+
+ <!-- Drag/Pan the viewport -->
+ <input type="image" alt="viewport drag" src="app/images/drag.svg"
+ id="noVNC_view_drag_button" class="noVNC_button noVNC_hidden"
+ title="Move/Drag Viewport">
+
+ <!--noVNC Touch Device only buttons-->
+ <div id="noVNC_mobile_buttons">
+ <input type="image" alt="No mousebutton" src="app/images/mouse_none.svg"
+ id="noVNC_mouse_button0" class="noVNC_button"
+ title="Active Mouse Button">
+ <input type="image" alt="Left mousebutton" src="app/images/mouse_left.svg"
+ id="noVNC_mouse_button1" class="noVNC_button"
+ title="Active Mouse Button">
+ <input type="image" alt="Middle mousebutton" src="app/images/mouse_middle.svg"
+ id="noVNC_mouse_button2" class="noVNC_button"
+ title="Active Mouse Button">
+ <input type="image" alt="Right mousebutton" src="app/images/mouse_right.svg"
+ id="noVNC_mouse_button4" class="noVNC_button"
+ title="Active Mouse Button">
+ <input type="image" alt="Keyboard" src="app/images/keyboard.svg"
+ id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard">
+ </div>
+
+ <!-- Extra manual keys -->
+ <div id="noVNC_extra_keys">
+ <input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
+ id="noVNC_toggle_extra_keys_button" class="noVNC_button"
+ title="Show Extra Keys">
+ <div class="noVNC_vcenter">
+ <div id="noVNC_modifiers" class="noVNC_panel">
+ <input type="image" alt="Ctrl" src="app/images/ctrl.svg"
+ id="noVNC_toggle_ctrl_button" class="noVNC_button"
+ title="Toggle Ctrl">
+ <input type="image" alt="Alt" src="app/images/alt.svg"
+ id="noVNC_toggle_alt_button" class="noVNC_button"
+ title="Toggle Alt">
+ <input type="image" alt="Windows" src="app/images/windows.svg"
+ id="noVNC_toggle_windows_button" class="noVNC_button"
+ title="Toggle Windows">
+ <input type="image" alt="Tab" src="app/images/tab.svg"
+ id="noVNC_send_tab_button" class="noVNC_button"
+ title="Send Tab">
+ <input type="image" alt="Esc" src="app/images/esc.svg"
+ id="noVNC_send_esc_button" class="noVNC_button"
+ title="Send Escape">
+ <input type="image" alt="Ctrl+Alt+Del" src="app/images/ctrlaltdel.svg"
+ id="noVNC_send_ctrl_alt_del_button" class="noVNC_button"
+ title="Send Ctrl-Alt-Del">
+ </div>
+ </div>
+ </div>
+
+ <!-- Shutdown/Reboot -->
+ <input type="image" alt="Shutdown/Reboot" src="app/images/power.svg"
+ id="noVNC_power_button" class="noVNC_button"
+ title="Shutdown/Reboot...">
+ <div class="noVNC_vcenter">
+ <div id="noVNC_power" class="noVNC_panel">
+ <div class="noVNC_heading">
+ <img alt="" src="app/images/power.svg"> Power
+ </div>
+ <input type="button" id="noVNC_shutdown_button" value="Shutdown">
+ <input type="button" id="noVNC_reboot_button" value="Reboot">
+ <input type="button" id="noVNC_reset_button" value="Reset">
+ </div>
+ </div>
+
+ <!-- Clipboard -->
+ <input type="image" alt="Clipboard" src="app/images/clipboard.svg"
+ id="noVNC_clipboard_button" class="noVNC_button"
+ title="Clipboard">
+ <div class="noVNC_vcenter">
+ <div id="noVNC_clipboard" class="noVNC_panel">
+ <div class="noVNC_heading">
+ <img alt="" src="app/images/clipboard.svg"> Clipboard
+ </div>
+ <textarea id="noVNC_clipboard_text" rows=5></textarea>
+ <br>
+ <input id="noVNC_clipboard_clear_button" type="button"
+ value="Clear" class="noVNC_submit">
+ </div>
+ </div>
+
+ <!-- Toggle fullscreen -->
+ <input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
+ id="noVNC_fullscreen_button" class="noVNC_button noVNC_hidden"
+ title="Fullscreen">
+
+ <!-- Settings -->
+ <input type="image" alt="Settings" src="app/images/settings.svg"
+ id="noVNC_settings_button" class="noVNC_button"
+ title="Settings">
+ <div class="noVNC_vcenter">
+ <div id="noVNC_settings" class="noVNC_panel">
+ <ul>
+ <li class="noVNC_heading">
+ <img alt="" src="app/images/settings.svg"> Settings
+ </li>
+ <li>
+ <label><input id="noVNC_setting_shared" type="checkbox"> Shared Mode</label>
+ </li>
+ <li>
+ <label><input id="noVNC_setting_view_only" type="checkbox"> View Only</label>
+ </li>
+ <li><hr></li>
+ <li>
+ <label><input id="noVNC_setting_view_clip" type="checkbox"> Clip to Window</label>
+ </li>
+ <li>
+ <label for="noVNC_setting_resize">Scaling Mode:</label>
+ <select id="noVNC_setting_resize" name="vncResize">
+ <option value="off">None</option>
+ <option value="scale">Local Scaling</option>
+ <option value="remote">Remote Resizing</option>
+ </select>
+ </li>
+ <li><hr></li>
+ <li>
+ <div class="noVNC_expander">Advanced</div>
+ <div><ul>
+ <li>
+ <label for="noVNC_setting_repeaterID">Repeater ID:</label>
+ <input id="noVNC_setting_repeaterID" type="text" value="">
+ </li>
+ <li>
+ <div class="noVNC_expander">WebSocket</div>
+ <div><ul>
+ <li>
+ <label><input id="noVNC_setting_encrypt" type="checkbox"> Encrypt</label>
+ </li>
+ <li>
+ <label for="noVNC_setting_host">Host:</label>
+ <input id="noVNC_setting_host">
+ </li>
+ <li>
+ <label for="noVNC_setting_port">Port:</label>
+ <input id="noVNC_setting_port" type="number">
+ </li>
+ <li>
+ <label for="noVNC_setting_path">Path:</label>
+ <input id="noVNC_setting_path" type="text" value="websockify">
+ </li>
+ </ul></div>
+ </li>
+ <li><hr></li>
+ <li>
+ <label><input id="noVNC_setting_reconnect" type="checkbox"> Automatic Reconnect</label>
+ </li>
+ <li>
+ <label for="noVNC_setting_reconnect_delay">Reconnect Delay (ms):</label>
+ <input id="noVNC_setting_reconnect_delay" type="number">
+ </li>
+ <li><hr></li>
+ <li>
+ <label><input id="noVNC_setting_show_dot" type="checkbox"> Show Dot when No Cursor</label>
+ </li>
+ <li><hr></li>
+ <!-- Logging selection dropdown -->
+ <li>
+ <label>Logging:
+ <select id="noVNC_setting_logging" name="vncLogging">
+ </select>
+ </label>
+ </li>
+ </ul></div>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ <!-- Connection Controls -->
+ <input type="image" alt="Disconnect" src="app/images/disconnect.svg"
+ id="noVNC_disconnect_button" class="noVNC_button"
+ title="Disconnect">
+
+ </div>
+ </div>
+
+ <div id="noVNC_control_bar_hint"></div>
+
+ </div> <!-- End of noVNC_control_bar -->
+
+ <!-- Status Dialog -->
+ <div id="noVNC_status"></div>
+
+ <!-- Connect button -->
+ <div class="noVNC_center">
+ <div id="noVNC_connect_dlg">
+ <div class="noVNC_logo" translate="no"><span>no</span>VNC</div>
+ <div id="noVNC_connect_button"><div>
+ <img alt="" src="app/images/connect.svg"> Connect
+ </div></div>
+ </div>
+ </div>
+
+ <!-- Password Dialog -->
+ <div class="noVNC_center noVNC_connect_layer">
+ <div id="noVNC_password_dlg" class="noVNC_panel"><form>
+ <ul>
+ <li>
+ <label>Password:</label>
+ <input id="noVNC_password_input" type="password">
+ </li>
+ <li>
+ <input id="noVNC_password_button" type="submit" value="Send Password" class="noVNC_submit">
+ </li>
+ </ul>
+ </form></div>
+ </div>
+
+ <!-- Transition Screens -->
+ <div id="noVNC_transition">
+ <div id="noVNC_transition_text"></div>
+ <div>
+ <input type="button" id="noVNC_cancel_reconnect_button" value="Cancel" class="noVNC_submit">
+ </div>
+ <div class="noVNC_spinner"></div>
+ </div>
+
+ <!-- This is where the RFB elements will attach -->
+ <div id="noVNC_container">
+ <!-- Note that Google Chrome on Android doesn't respect any of these,
+ html attributes which attempt to disable text suggestions on the
+ on-screen keyboard. Let's hope Chrome implements the ime-mode
+ style for example -->
+ <textarea id="noVNC_keyboardinput" autocapitalize="off"
+ autocomplete="off" spellcheck="false" tabindex="-1"></textarea>
+ </div>
+
+ <audio id="noVNC_bell">
+ <source src="app/sounds/bell.oga" type="audio/ogg">
+ <source src="app/sounds/bell.mp3" type="audio/mpeg">
+ </audio>
+ </body>
+</html>
diff --git a/systemvm/agent/noVNC/vnc_lite.html b/systemvm/agent/noVNC/vnc_lite.html
new file mode 100644
index 0000000..12ac1d5
--- /dev/null
+++ b/systemvm/agent/noVNC/vnc_lite.html
@@ -0,0 +1,219 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+
+ <!--
+ noVNC example: lightweight example using minimal UI and features
+
+ This is a self-contained file which doesn't import WebUtil or external CSS.
+
+ Copyright (C) 2018 The noVNC Authors
+ noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+
+ Connect parameters are provided in query string:
+ http://example.com/?host=HOST&port=PORT&scale=true
+ -->
+ <title>noVNC</title>
+
+ <meta charset="utf-8">
+
+ <style>
+
+ body {
+ margin: 0;
+ background-color: dimgrey;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ }
+ html {
+ height: 100%;
+ }
+
+ #top_bar {
+ background-color: #6e84a3;
+ color: white;
+ font: bold 12px Helvetica;
+ padding: 6px 5px 4px 5px;
+ border-bottom: 1px outset;
+ }
+ #status {
+ text-align: center;
+ }
+ #sendCtrlAltDelButton {
+ position: fixed;
+ top: 0px;
+ right: 0px;
+ border: 1px outset;
+ padding: 5px 5px 4px 5px;
+ cursor: pointer;
+ }
+ #sendCtrlEscButton {
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ border: 1px outset;
+ padding: 5px 5px 4px 5px;
+ cursor: pointer;
+ }
+ #screen {
+ flex: 1; /* fill remaining space */
+ overflow: hidden;
+ }
+
+ </style>
+
+ <!-- Promise polyfill for IE11 -->
+ <script src="vendor/promise.js"></script>
+
+ <!-- ES2015/ES6 modules polyfill -->
+ <script type="module">
+ window._noVNC_has_module_support = true;
+ </script>
+ <script>
+ window.addEventListener("load", function() {
+ if (window._noVNC_has_module_support) return;
+ const loader = document.createElement("script");
+ loader.src = "vendor/browser-es-module-loader/dist/" +
+ "browser-es-module-loader.js";
+ document.head.appendChild(loader);
+ });
+ </script>
+
+ <!-- actual script modules -->
+ <script type="module" crossorigin="anonymous">
+ // RFB holds the API to connect and communicate with a VNC server
+ import RFB from './core/rfb.js';
+
+ let rfb;
+ let desktopName;
+
+ // When this function is called we have
+ // successfully connected to a server
+ function connectedToServer(e) {
+ status("Connected");
+ }
+
+ // This function is called when we are disconnected
+ function disconnectedFromServer(e) {
+ if (e.detail.clean) {
+ status("Disconnected");
+ } else {
+ status("Something went wrong, connection is closed");
+ }
+ }
+
+ // When this function is called, the server requires
+ // credentials to authenticate
+ function credentialsAreRequired(e) {
+ const password = prompt("Password Required:");
+ rfb.sendCredentials({ password: password });
+ }
+
+ // When this function is called we have received
+ // a desktop name from the server
+ function updateDesktopName(e) {
+ desktopName = e.detail.name;
+ }
+
+ // Since most operating systems will catch Ctrl+Alt+Del
+ // before they get a chance to be intercepted by the browser,
+ // we provide a way to emulate this key sequence.
+ function sendCtrlAltDel() {
+ rfb.sendCtrlAltDel();
+ return false;
+ }
+
+ function sendCtrlEsc() {
+ rfb.sendCtrlEsc();
+ return false;
+ }
+
+ // Show a status text in the top bar
+ function status(text) {
+ document.getElementById('status').textContent = text;
+ }
+
+ // This function extracts the value of one variable from the
+ // query string. If the variable isn't defined in the URL
+ // it returns the default value instead.
+ function readQueryVariable(name, defaultValue) {
+ // A URL with a query parameter can look like this:
+ // https://www.example.com?myqueryparam=myvalue
+ //
+ // Note that we use location.href instead of location.search
+ // because Firefox < 53 has a bug w.r.t location.search
+ const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
+ match = document.location.href.match(re);
+ if (typeof defaultValue === 'undefined') { defaultValue = null; }
+
+ if (match) {
+ // We have to decode the URL since want the cleartext value
+ return decodeURIComponent(match[1]);
+ }
+
+ return defaultValue;
+ }
+
+ document.getElementById('sendCtrlAltDelButton')
+ .onclick = sendCtrlAltDel;
+
+ document.getElementById('sendCtrlEscButton')
+ .onclick = sendCtrlEsc;
+
+ // Read parameters specified in the URL query string
+ // By default, use the host and port of server that served this file
+ const host = readQueryVariable('host', window.location.hostname);
+ let port = readQueryVariable('port', window.location.port);
+ const password = readQueryVariable('password', '');
+ const path = readQueryVariable('path', 'websockify');
+
+ // | | | | | |
+ // | | | Connect | | |
+ // v v v v v v
+
+ status("Connecting");
+
+ // Build the websocket URL used to connect
+ let url;
+ if (window.location.protocol === "https:") {
+ url = 'wss';
+ } else {
+ url = 'ws';
+ }
+ url += '://' + host;
+ if(port) {
+ url += ':' + port;
+ }
+ url += '/' + path;
+ const token = readQueryVariable('token', '');
+ url += '?token=' + token;
+
+ // Creating a new RFB object will start a new connection
+ rfb = new RFB(document.getElementById('screen'), url,
+ { credentials: { password: password } });
+
+ // Add listeners to important events from the RFB module
+ rfb.addEventListener("connect", connectedToServer);
+ rfb.addEventListener("disconnect", disconnectedFromServer);
+ rfb.addEventListener("credentialsrequired", credentialsAreRequired);
+ rfb.addEventListener("desktopname", updateDesktopName);
+
+ // Set parameters that can be changed on an active connection
+ rfb.viewOnly = readQueryVariable('view_only', false);
+ rfb.scaleViewport = readQueryVariable('scale', false);
+ </script>
+</head>
+
+<body>
+ <div id="top_bar">
+ <div id="status">Loading</div>
+ <div id="sendCtrlAltDelButton">Send CtrlAltDel</div>
+ <div id="sendCtrlEscButton">Send CtrlEsc</div>
+ </div>
+ <div id="screen">
+ <!-- This is where the remote screen will appear -->
+ </div>
+</body>
+</html>
diff --git a/systemvm/debian/etc/iptables/iptables-consoleproxy b/systemvm/debian/etc/iptables/iptables-consoleproxy
index 9a1c985..631a4b0 100644
--- a/systemvm/debian/etc/iptables/iptables-consoleproxy
+++ b/systemvm/debian/etc/iptables/iptables-consoleproxy
@@ -35,4 +35,5 @@
-A INPUT -i eth1 -p tcp -m state --state NEW -m tcp --dport 8001 -j ACCEPT
-A INPUT -i eth2 -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT
-A INPUT -i eth2 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
+-A INPUT -i eth2 -p tcp -m state --state NEW -m tcp --dport 8080 -j ACCEPT
COMMIT
diff --git a/systemvm/systemvm-agent-descriptor.xml b/systemvm/systemvm-agent-descriptor.xml
index a3f0453..74b1543 100644
--- a/systemvm/systemvm-agent-descriptor.xml
+++ b/systemvm/systemvm-agent-descriptor.xml
@@ -112,5 +112,14 @@
<include>*.key</include>
</includes>
</fileSet>
+ <fileSet>
+ <directory>agent/noVNC</directory>
+ <outputDirectory>noVNC</outputDirectory>
+ <directoryMode>555</directoryMode>
+ <fileMode>555</fileMode>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </fileSet>
</fileSets>
</assembly>