=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/PushMessagesWorker.java' --- src/com/goldencode/p2j/ui/client/driver/web/PushMessagesWorker.java 2015-05-18 20:48:28 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/PushMessagesWorker.java 2016-02-09 18:52:59 +0000 @@ -11,6 +11,8 @@ ** -#- -I- --Date-- ----------------------Description-------------------------- ** 001 MAG 20140220 Created initial version. ** 002 GES 20150308 Moved to a more generic package location. +** 003 SBI 20160209 Implemented sending messages that can exceed the limit message size defined +** for the current web socket session. */ package com.goldencode.p2j.ui.client.driver.web; @@ -147,6 +149,43 @@ } /** + * Sends a binary message in packets that don't exceed the maximum binary message size for + * the current web socket session. + * + * @param message + * The binary message that should be send in packets. + * @throws IOException + * The exception that can be thrown during the web socket write operation. + */ + private void sendBytesByPackets(ByteBuffer message) throws IOException + { + int messageLimit = session.getPolicy().getMaxBinaryMessageSize(); + boolean isLast; + message.position(0); + message.limit(message.capacity()); + while(message.hasRemaining()) + { + System.err.println("message.remaining()=" + message.remaining()); + if (message.remaining() > messageLimit) + { + isLast = false; + message.limit(messageLimit + message.position()); + } + else + { + isLast = true; + } + remote.sendPartialBytes(message, isLast); + message.position(message.limit()); + message.limit(message.capacity()); + System.err.println("message.position()=" + message.position()); + System.err.println("message.limit()=" + message.limit()); + System.err.println("message.remaining()=" + message.remaining()); + System.err.println("message.hasRemaining()=" + message.hasRemaining()); + } + } + + /** * Send a message. */ private void sendMessage() @@ -166,6 +205,7 @@ // test message type if (message instanceof ByteBuffer) { + //sendBytesByPackets((ByteBuffer) message); remote.sendBytes((ByteBuffer) message); } else if (message instanceof String) === modified file 'src/com/goldencode/p2j/ui/client/driver/web/index.html' --- src/com/goldencode/p2j/ui/client/driver/web/index.html 2015-12-17 20:00:50 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/index.html 2016-02-07 17:47:14 +0000 @@ -18,6 +18,7 @@ + === added file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.connector.js' --- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.connector.js 1970-01-01 00:00:00 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.connector.js 2016-02-09 19:52:55 +0000 @@ -0,0 +1,665 @@ +/* +** Module : p2j.connector.js +** Abstract : Defines the connection module that is responsible for the web socket management. +** +** Copyright (c) 2015-2016, Golden Code Development Corporation. +** ALL RIGHTS RESERVED. Use is subject to license terms. +** +** Golden Code Development Corporation +** CONFIDENTIAL +** +** -#- -I- --Date-- ----------------------Description-------------------------- +** 001 SBI 20160209 Implemented the module that manages web socket connection and is executed by +** the dedicated web worker thread. +*/ +"use strict"; + +importScripts("/common/p2j.io.js"); + +/** The low level web socket */ +var ws; + +/** The connection status */ +var connected = false; + +/** Input and output channel that is the helper class for read and write web socket operations */ +var io = InputOutputChannel( + function(data) + { + if (connected) + { + ws.send(data); + } + }); + +/** + * The web worker message handler that listens messages from the UI thread. + * + * @param {MessageEvent} msg + * The message from the UI thread. + */ +self.onmessage = function(msg) +{ + var data = msg.data; + switch(data.type) + { + case MSG.MSG_WEB_SOCKET_OPEN: + var url = data.url; + try + { + createWebsocket(url); + } + catch(ex) + { + self.postMessage({type : MSG.MSG_WEB_SOCKET_ERROR}); + } + break; + case MSG.MSG_WEB_SOCKET_CLOSE: + if (connected) + { + connected = false; + ws.close(); + } + break; + case MSG.MSG_PAGE_LOADED: + // notify the web socket has opened and the page is loaded + io.sendNotification(MSG.MSG_PAGE_LOADED); + break; + case MSG.MSG_WEB_SOCKET_SEND: + io.send(data.message); + break; + } +}; + +/** Web socket message handler */ +function messageHandler(message) +{ + var type = message[0]; + switch (type) + { + case MSG.MSG_CLEAR: + case MSG.MSG_BEEP: + case MSG.MSG_QUIT: + self.postMessage({type : type}); + break; + case MSG.MSG_DRAW: + // remove the MD5 code and save it here + var md5a = new message.constructor(16); + message = io.splice(message, 1, 16, md5a); + var md5 = io.createHexString(md5a); + console.debug("draw buffer length=" + message.buffer.byteLength); + self.postMessage({type : type, md5 : md5, drawBuffer : message.buffer}, [message.buffer]); + break; + case MSG.MSG_CURSOR_POS: + self.postMessage({type : type, col : message[1], row : message[2]}); + break; + case MSG.MSG_CURSOR_STATUS: + self.postMessage({type : type, status : message[1]}); + break; + case MSG.MSG_VT100: + self.postMessage({type : type, vt100 : (message[1] == 0) ? false : true}); + break; + case MSG.MSG_READ_CLIPBOARD: + self.postMessage({type : type}); + break; + case MSG.MSG_WRITE_CLIPBOARD: + // The clipboard is changed. + var text = io.readStringBinaryMessage(message, 1); + self.postMessage({type : type, clipboardText : text}); + break; + case MSG.MSG_CREATE_WINDOW: + // create a top-level window with the given id + var id = io.readInt32BinaryMessage(message, 1); + self.postMessage({type : type, id : id}); + break; + case MSG.MSG_CREATE_CHILD_WINDOW: + // create a child window with the given id, owner and title + var id = io.readInt32BinaryMessage(message, 1); + var owner = io.readInt32BinaryMessage(message, 5); + var title = io.readStringBinaryMessage(message, 9); + self.postMessage({type : type, id : id, owner : owner, title : title}); + break; + case MSG.MSG_DESTROY_WINDOW: + // destroy top-level or child window + var id = io.readInt32BinaryMessage(message, 1); + var numberImages = io.readInt32BinaryMessage(message, 5); + var images = new ArrayBuffer(numberImages * 4); + var imagesView = new Int32Array(images); + + for (var i = 0; i < numberImages; i++) + { + imagesView[i] = io.readInt32BinaryMessage(message, 9 + (i * 4)); + } + self.postMessage({type : type, id : id, numberImages : numberImages, images : images}, + [images]); + break; + case MSG.MSG_WINDOW_VISIBILITY: + // change visibility for top-level or child window + var id = io.readInt32BinaryMessage(message, 1); + var visible = message[5] === 0 ? false : true; + self.postMessage({type : type, id : id, visible : visible}); + break; + // font and metrics related requests + case MSG.MSG_GET_PAR_HEIGHT: + // paragraph height + + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var textLength = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var text = io.readStringBinaryMessageByLength(message, offset, textLength); + offset = offset + textLength * 2; + + var font = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var maxWidth = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + self.postMessage( + { + type : type, msgId : msgId, textLength : textLength, + text : text, font : font, maxWidth : maxWidth + }); + break; + case MSG.MSG_GET_TEXT_HEIGHT: + // text height + + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var textLength = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var text = io.readStringBinaryMessageByLength(message, offset, textLength); + offset = offset + textLength * 2; + + var font = io.readInt32BinaryMessage(message, offset); + + self.postMessage( + { + type : type, msgId : msgId, textLength : textLength, + text : text, font : font + }); + break; + case MSG.MSG_GET_TEXT_WIDTH: + // text width + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var textLength = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var text = io.readStringBinaryMessageByLength(message, offset, textLength); + offset = offset + textLength * 2; + + var font = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + self.postMessage( + { + type : type, msgId : msgId, textLength : textLength, + text : text, font : font + }); + break; + case MSG.MSG_GET_FONT_HEIGHT: + // font height + + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var font = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + self.postMessage( + { + type : type, msgId : msgId, font : font + }); + break; + case MSG.MSG_GET_FONT_WIDTHS: + // font widths + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var font = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + self.postMessage( + { + type : type, msgId : msgId, font : font + }); + break; + case MSG.MSG_CREATE_FONT: + // create font + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var font = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var nameLength = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + var name = io.readStringBinaryMessageByLength(message, offset, nameLength); + offset = offset + nameLength * 2; + + var size = message[offset]; + offset = offset + 1; + + var style = message[offset]; + offset = offset + 1; + + var defLength = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var b64font = ""; + if (defLength > 0) + { + var binFont = ''; + for (var i = 0; i < defLength; i++) + { + binFont += String.fromCharCode(message[offset]); + offset = offset + 1; + } + b64font = self.btoa(binFont); + } + + self.postMessage( + { + type : type, msgId : msgId, font : font, nameLength : nameLength, + name : name, size : size, style : style, b64font : b64font + }); + break; + case MSG.MSG_DERIVE_FONT: + // derive font + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var font = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + self.postMessage( + { + type : type, msgId : msgId, font : font + }); + break; + case MSG.MSG_SET_CURSOR_STYLE: + // set cursor style + var styleId = io.readInt32BinaryMessage(message, 1); + var wid = io.readInt32BinaryMessage(message, 5); + self.postMessage( + { + type : type, styleId : styleId, wid : wid + }); + break; + case MSG.MSG_RESTACK_WINDOWS: + // restack windows + var num = io.readInt32BinaryMessage(message, 1); + var winids = new ArrayBuffer(num * 4); + var winidsView = new Int32Array(winids); + for (var i = 0; i < num; i++) + { + winidsView[i] = io.readInt32BinaryMessage(message, 5 + (i * 4)); + } + self.postMessage({type : type, winids : winids}, [winids]); + break; + case MSG.PROCESS_MOUSE_WIDGETS: + // register/deregister widgets for mouse actions + var offset = 1; + + // the number of windows with new widgets + var windowNo = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + for (var j = 0; j < windowNo; j++) + { + // the window ID + var windowID = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + // the number of new widgets in this window + var widgetNo = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + var widgets = new ArrayBuffer(widgetNo * 4); + var widgetsView = new Int32Array(widgets); + + var xCoords = new ArrayBuffer(widgetNo * 2); + var xCoordsView = new Int16Array(xCoords); + + var yCoords = new ArrayBuffer(widgetNo * 2); + var yCoordsView = new Int16Array(yCoords); + + var widths = new ArrayBuffer(widgetNo * 2); + var widthsView = new Int16Array(widths); + + var heights = new ArrayBuffer(widgetNo * 2); + var heightsView = new Int16Array(heights); + + var actions = new ArrayBuffer(widgetNo * 4); + var actionsView = new Int32Array(actions); + + for (var k = 0; k < widgetNo; k++) + { + // the widget ID + widgetsView[k] = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + // the coordinates + xCoordsView[k] = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + yCoordsView[k] = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + // the size + widthsView[k] = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + heightsView[k] = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + // bit-encoded mouse ops: each bit from 1 to 11, if set, represents a mouse + // operation as defined in p2j.screen.mouseOps + actionsView[k] = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + } + var allWNo = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + var zOrder = new ArrayBuffer(allWNo * 4); + var zOrderView = new Int32Array(zOrder); + + for (var k = 0; k < allWNo; k++) + { + zOrderView[k] = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + } + self.postMessage( + { + type : type, label : "registerWidgets", windowID : windowID, widgetNo : widgetNo, + widgets : widgets, + xCoords : xCoords, yCoords : yCoords, + widths: widths, heights : heights, + actions : actions, + zOrder : zOrder + }, [widgets, xCoords, yCoords, widths, heights, actions, zOrder]); + } + + // the number of windows with dead widgets + windowNo = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + for (var j = 0; j < windowNo; j++) + { + // the window ID + var windowID = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + // the number of dead widgets in this window + var widgetNo = io.readInt16BinaryMessage(message, offset); + + var widgets = new ArrayBuffer(widgetNo * 4); + var widgetsView = new Int32Array(widgets); + + offset = offset + 2; + + for (var k = 0; k < widgetNo; k++) + { + // the widget ID + widgetsView[k] = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + } + self.postMessage( + { + type : type, label : "deregisterWidgets", + windowID : windowID, widgetNo : widgetNo, + widgets : widgets + }, [widgets]); + } + + // the number of windows with new "any widgets" + windowNo = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + for (var j = 0; j < windowNo; j++) + { + // the window ID + var windowID = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + // the number of new "any widgets" in this window + var widgetNo = io.readInt16BinaryMessage(message, offset); + + var widgets = new ArrayBuffer(widgetNo * 4); + var widgetsView = new Int32Array(widgets); + + offset = offset + 2; + + for (var k = 0; k < widgetNo; k++) + { + // the widget ID + widgetsView[k] = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + } + self.postMessage( + { + type : type, label : "registerAnyWidgets", + windowID : windowID, widgetNo : widgetNo, + widgets : widgets + }, [widgets]); + } + + // the number of windows with dead "any widgets" + windowNo = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + + for (var j = 0; j < windowNo; j++) + { + // the window ID + var windowID = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + // the number of dead "any widgets" in this window + var widgetNo = io.readInt16BinaryMessage(message, offset); + + var widgets = new ArrayBuffer(widgetNo * 4); + var widgetsView = new Int32Array(widgets); + + offset = offset + 2; + + for (var k = 0; k < widgetNo; k++) + { + // the widget ID + widgetsView[k] = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + } + self.postMessage( + { + type : type, label : "deregisterAnyWidgets", + windowID : windowID, widgetNo : widgetNo, + widgets : widgets, + + }, [widgets]); + } + break; + case MSG.CAPTURE_MOUSE: + self.postMessage({type : type, capture : (message[1] == 1)}); + break; + case MSG.ENABLE_OS_EVENTS: + // enable/disable OS events + var wid = io.readInt32BinaryMessage(message, 1); + self.postMessage( + { + type : type, + windowID : wid, + enable : message[5] == 1 + }); + break; + case MSG.SET_ICONIFICATION_STATE: + // set window iconification state + var offset = 1; + + var windowID = io.readInt32BinaryMessage(message, offset); + self.postMessage( + { + type : type, + windowID : windowID, + iconified : message[5] == 1 + }); + break; + case MSG.SET_RESIZEABLE_WINDOW: + // resizeable window + var offset = 1; + + var windowID = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var resizeable = (message[offset] == 1); + offset = offset + 1; + + var msg = {type : type, windowID : windowID, resizeable : resizeable}; + + if (resizeable) + { + msg.minWidth = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + msg.minHeight = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + msg.maxWidth = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + msg.maxHeight = io.readInt16BinaryMessage(message, offset); + offset = offset + 2; + } + self.postMessage(msg); + break; + case MSG.MSG_CURRENT_SELECTION: + // current editors selection is changed + var text = io.readStringBinaryMessage(message, 1); + self.postMessage({type : type, clipboardText : text}); + break; + case MSG.MSG_MOVE_TO_TOP: + case MSG.MSG_MOVE_TO_BOTTOM: + var id = io.readInt32BinaryMessage(message, 1); + self.postMessage({type : type, windowID : id}); + break; + case MSG.MSG_WINDOW_SENSITIVITY: + // change sensitivity for top-level or child window + var id = io.readInt32BinaryMessage(message, 1); + var enabled = message[5] === 0 ? false : true; + self.postMessage({type : type, windowID : id, enabled : enabled}); + break; + case MSG.MSG_IS_FONT_INSTALLED: + // font is installed + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var fontNameLength = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + var fontName = io.readStringBinaryMessageByLength(message, offset, fontNameLength); + + self.postMessage({type : type, msgId : msgId, fontName : fontName}); + break; + case MSG.MSG_REMOVE_EXPIRED_HASH: + // remove hashes + var offset = 1; + + var msgId = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + // window ID + var windowID = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + + // number of hashes + var hashNo = io.readInt32BinaryMessage(message, offset); + offset = offset + 4; + var hashes = message.slice(offset); + console.debug("hashes buffer length=" + hashes.buffer.byteLength); + self.postMessage( + { + type : type, msgId : msgId, windowID : windowID, hashNo : hashNo, + hashes : hashes.buffer + }, [hashes.buffer]); + break; + }; +} + +/** + * Creates a web socket connection and attaches the error, message and close listeners for it. + * + * @param {String} url + * The web socket connection url + */ +function createWebsocket(url) +{ + ws = new WebSocket(url); + + /** + * Handles the web socket open message. + */ + ws.onopen = function() + { + connected = true; + ws.binaryType = 'arraybuffer'; + + self.postMessage({type : MSG.MSG_WEB_SOCKET_OPEN}); + + }; + + /** + * Handles the web socket data messages. + */ + ws.onmessage = function(evt) + { + var data = evt.data; + console.debug("evt=" + JSON.stringify(evt)); + console.debug("data=" + JSON.stringify(data)); + if (data instanceof ArrayBuffer) + { + // binary messages + var message = new Uint8Array(data); + messageHandler(message); + } + else + { + // text messages + var msg = JSON.parse(data); + self.postMessage({type : MSG.MSG_COLOR_PALETTE, msg : msg}); + } + }; + + /** + * Handles the web socket close message. + */ + ws.onclose = function() + { + connected = false; + self.postMessage({type : MSG.MSG_WEB_SOCKET_CLOSE}); + }; + + /** + * Handles the web socket errors. + */ + ws.onerror = function(ex) + { + console.debug("exception=" + ex); + //self.postMessage({type : MSG.MSG_WEB_SOCKET_ERROR}); + }; +} \ No newline at end of file === added file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.io.js' --- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.io.js 1970-01-01 00:00:00 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.io.js 2016-02-09 17:40:29 +0000 @@ -0,0 +1,667 @@ +/* +** Module : p2j.io.js +** Abstract : Defines the common message's types and the helper input and output channel. +** +** Copyright (c) 2015-2016, Golden Code Development Corporation. +** ALL RIGHTS RESERVED. Use is subject to license terms. +** +** Golden Code Development Corporation +** CONFIDENTIAL +** +** -#- -I- --Date-- ----------------------Description-------------------------- +** 001 SBI 20160209 Reused the common input and output functions under the input and output +** channel object. +*/ +"use strict"; + +/** + * Defines the common message's types. + */ +var MSG = { + /** Message that the web page has finished loading/initializing. */ + MSG_PAGE_LOADED : 0xFF, + + /** Progress compatible keyboard message. */ + MSG_KEY : 0x01, + + /** Virtual VT100 terminal keyboard message. */ + MSG_KEY_VT100 : 0x02, + + /** Paste message. */ + MSG_PASTE : 0x03, + + /** Contents from the clipboard. */ + MSG_CLIPBOARD_CONTENTS : 0x04, + + /** Web client is requesting the current selection text. */ + MSG_CLIPBOARD_PREPARE : 0x05, + + /** Web client is sending computed paragraph height. */ + MSG_SENT_PAR_HEIGHT : 0x06, + + /** Web client is sending computed text height. */ + MSG_SENT_TEXT_HEIGHT : 0x07, + + /** Web client is sending computed text width. */ + MSG_SENT_TEXT_WIDTH : 0x08, + + /** Web client is sending computed font height. */ + MSG_SENT_FONT_HEIGHT : 0x09, + + /** Web client is sending computed font widths. */ + MSG_SENT_FONT_WIDTHS : 0x0A, + + /** Web client is informing the font has been created. */ + MSG_DONE_CREATE_FONT : 0x0B, + + /** Web client is informing the font has been derived. */ + MSG_DONE_DERIVE_FONT : 0x0C, + + /** Web client is informing of a mouse event. */ + MSG_MOUSE_EVENT : 0x0D, + + /** Web client is informing of new window location. */ + MSG_SET_WINDOW_LOC : 0x0E, + + /** Web client is informing of new top window. */ + MSG_WINDOW_ACTIVATED : 0x0F, + + /** Web client is informing of new window iconification state. */ + MSG_WINDOW_ICONIFY : 0x10, + + /** Web client is informing of new window location/dimension after resize. */ + MSG_WINDOW_RESIZED : 0x11, + + /** Web client is informing it finished removing the hashes. */ + MSG_DRAW_HASH_REMOVED : 0x12, + + /** Clear screen message. */ + MSG_CLEAR : 0x80, + + /** Draw text on screen. */ + MSG_DRAW : 0x81, + + /** Set cursor position. */ + MSG_CURSOR_POS : 0x82, + + /** Set cursor status. */ + MSG_CURSOR_STATUS : 0x83, + + /** Beep message. */ + MSG_BEEP : 0x84, + + /** Quit message. */ + MSG_QUIT : 0x85, + + /** Switch input processing to virtual VT100 terminal mode. */ + MSG_VT100 : 0x86, + + /** Request clipboard contents from web client. */ + MSG_READ_CLIPBOARD : 0x87, + + /** Copy the given text to the web client's clipboard. */ + MSG_WRITE_CLIPBOARD : 0x88, + + /** Create a new top-level window. */ + MSG_CREATE_WINDOW : 0x89, + + /** Create a new child window. */ + MSG_CREATE_CHILD_WINDOW : 0x8A, + + /** Destroy the specified window or child window. */ + MSG_DESTROY_WINDOW : 0x8B, + + /** Change the visibility for the specified window or child window. */ + MSG_WINDOW_VISIBILITY : 0x8C, + + /** Ask the client for the paragraph height. */ + MSG_GET_PAR_HEIGHT : 0x8D, + + /** Ask the client for the text height. */ + MSG_GET_TEXT_HEIGHT : 0x8E, + + /** Ask the client for the text width. */ + MSG_GET_TEXT_WIDTH : 0x8F, + + /** Ask the client for the font height. */ + MSG_GET_FONT_HEIGHT : 0x90, + + /** Ask the client for the font widths. */ + MSG_GET_FONT_WIDTHS : 0x91, + + /** Ask the client to create a font. */ + MSG_CREATE_FONT : 0x92, + + /** Ask the client to derive a font. */ + MSG_DERIVE_FONT : 0x93, + + /** Set the cursor style. */ + MSG_SET_CURSOR_STYLE : 0x94, + + /** Restack the z-order of all windows. */ + MSG_RESTACK_WINDOWS : 0x95, + + /** Send the mouse-aware widget details. */ + PROCESS_MOUSE_WIDGETS : 0x96, + + /** Disable/enable mouse event processing. */ + CAPTURE_MOUSE : 0x97, + + /** Disable/enable OS event processing. */ + ENABLE_OS_EVENTS : 0x98, + + /** Window state changed event. */ + SET_ICONIFICATION_STATE : 0x99, + + /** Resizeable widget. */ + SET_RESIZEABLE_WINDOW : 0x9A, + + /** Notifies the client about the current selection. */ + MSG_CURRENT_SELECTION : 0x9B, + + /** Requests the client to move a window to the top of the z-order. */ + MSG_MOVE_TO_TOP : 0x9C, + + /** Requests the client to move a window to the bottom of the z-order. */ + MSG_MOVE_TO_BOTTOM : 0x9D, + + /** Change the sensitivity for the specified window or child window. */ + MSG_WINDOW_SENSITIVITY : 0x9E, + + /** Requests the client if the target font is installed. */ + MSG_IS_FONT_INSTALLED : 0x9F, + + /** Inform the javascript side to remove the list of expired hashes. */ + MSG_REMOVE_EXPIRED_HASH : 0xA0, + + /** Color palette message. */ + MSG_COLOR_PALETTE : 0xA5, + + /** WEB SOCKET CLOSE */ + MSG_WEB_SOCKET_OPEN : 0xA1, + + /** WEB SOCKET CLOSE */ + MSG_WEB_SOCKET_CLOSE : 0xA2, + + /** WEB SOCKET ERROR */ + MSG_WEB_SOCKET_ERROR : 0xA3, + + /** WEB SOCKET SEND */ + MSG_WEB_SOCKET_SEND : 0xA4, + + }; + +/** + * Input and output channel. + * + * @param {Function} callback + * The callback function to which all output operations are deligated. + * + * @return {Object} + * The helper input and output channel. + */ +function InputOutputChannel(callback) +{ + + var me = {}; + + /** + * Send data. + * + * @param {array} + * data + */ + me.send = function(data) + { + callback(data); + }; + + /** + * Send a binary message with the given message type as the first and only + * byte. + * + * @param {byte} + * type Message type code. + */ + me.sendNotification = function(type) + { + var msg = new Uint8Array(1); + msg[0] = type; + + me.send(msg); + }; + + /** + * Sends the window active event to the server. + * + * @param {Number} + * wid The target window id + * @param {Boolean} + * active The flag indicating that the target window should be + * active or not. + * @param {Boolean} + * focusOut The true value indicates that the current focus is + * moved from a non P2J window to a P2J window for an activation + * event or from a P2j window to a non P2j window for a + * deactivation event. + */ + me.sendWindowActive = function(wid, active, focusOut) + { + // send the window activation/deactivation to the java side + var msg = new Uint8Array(7); + + // message type + msg[0] = 0x0f; + + // 1. the window ID + me.writeInt32BinaryMessage(msg, 1, wid); + + msg[5] = active ? 1 : 0; + msg[6] = focusOut ? 1 : 0; + + // send the window active event + me.send(msg); + } + + /** + * Sends the window icon state event to the server. + * + * @param {Number} + * wid The target window id. + * @param {Boolean} + * minimized The target window has been minimized. + */ + me.sendWindowIconState = function(wid, minimized) + { + var msg = new Uint8Array(6); + + // message type + msg[0] = 0x10; + + // 0. the window ID + me.writeInt32BinaryMessage(msg, 1, wid); + + // 1. the iconification state + msg[5] = minimized ? 1 : 0; + + // send the mouse event + me.send(msg); + } + + /** + * Send a binary message with the given message type and the specified byte array serialized. + * + * @param {byte} type + * Message type code. + * @param {int} msgId + * The message ID. + * @param {c} c + * The array to send. + */ + me.sendByteArrayBinaryMessage = function(type, msgId, c) + { + var msg = new Uint8Array(1 + 4 + 2 + c.length); + + var offset = 0; + msg[offset] = type; + offset = offset + 1; + + me.writeInt32BinaryMessage(msg, offset, msgId); + offset = offset + 4; + + msg[offset] = c.length & 0xff; + offset = offset + 1; + msg[offset] = (c.length >> 8) & 0xff + offset = offset + 1; + + for (var i = 0; i < c.length; i++) + { + msg[offset] = c[i] & 0xff; + offset = offset + 1; + } + + me.send(msg); + }; + + /** + * Send a binary message with the given message type and the specified byte as the second byte. + * + * @param {byte} type + * Message type code. + * @param {int} msgId + * The message ID. + * @param {c} c + * The byte to send. + */ + me.sendInt8BinaryMessage = function(type, msgId, c) + { + var msg = new Uint8Array(1 + 4 + 1); + + msg[0] = type; + me.writeInt32BinaryMessage(msg, 1, msgId); + msg[5] = c; + + me.send(msg); + }; + + /** + * Send a binary message with the given message type and the specified 16bit integer as the + * second and third bytes. + * + * @param {byte} type + * Message type code. + * @param {int} msgId + * The message ID. + * @param {c} c + * The 16bit integer to send. + */ + me.sendInt16BinaryMessage = function(type, msgId, c) + { + var msg = new Uint8Array(1 + 4 + 2); + + msg[0] = type; + me.writeInt32BinaryMessage(msg, 1, msgId); + me.writeInt16BinaryMessage(msg, 5, c); + + me.send(msg); + }; + + /** + * Send a binary message with the given message type and the specified 32bit integer as the + * second, third, forth and fifth bytes. + * + * @param {byte} type + * Message type code. + * @param {int} msgId + * The message ID. + * @param {c} c + * The 32bit integer to send. + */ + me.sendInt32BinaryMessage = function(type, msgId, c) + { + var msg = new Uint8Array(1 + 4 + 4); + + msg[0] = type; + me.writeInt32BinaryMessage(msg, 1, msgId); + me.writeInt32BinaryMessage(msg, 5, c); + + me.send(msg); + }; + + /** + * Send text as a binary message with the given message type as the first byte. + * + * @param {byte} type + * Message type code. + * @param {String} text + * The text to send. + */ + me.sendStringBinaryMessage = function(type, text) + { + var msg = new Uint8Array(text.length * 2 + 1); + msg[0] = type; + + for (var i = 0, j = 1; i < text.length; i++, j += 2) + { + var c = text.charCodeAt(i); + msg[j + 1] = c & 0xff; + c = c >> 8; + msg[j] = c & 0xff; + } + + me.send(msg); + }; + + /** + * Read text from a binary message with characters interpreted as 16-bit values. + * + * @param {byte[]} message + * Message bytes. + * @param {int} offset + * The location in the message at which the characters start. + */ + me.readStringBinaryMessage = function(message, offset) + { + var text = ""; + + for (var i = offset; i < message.length; i += 2) + { + var c = String.fromCharCode((message[i] << 8) | message[i + 1]); + + text = text + c; + } + + return text; + }; + + /** + * Read text from a binary message with characters interpreted as 16-bit values, with the + * specified length + * + * @param {byte[]} message + * Message bytes. + * @param {int} offset + * The location in the message at which the characters start. + * @param {len} length + * The length of the string. + */ + me.readStringBinaryMessageByLength = function(message, offset, len) + { + var text = ""; + + for (var i = offset; i < offset + len * 2; i += 2) + { + var c = String.fromCharCode((message[i] << 8) | message[i + 1]); + + text = text + c; + } + + return text; + }; + + /** + * Read a 32-bit integer from a binary message at a specific offset. + * + * @param {byte[]} message + * Message bytes. + * @param {int} offset + * The location in the message at which the characters start. + * + * @return The number that was read. + */ + me.readInt32BinaryMessage = function(message, offset) + { + var num = 0; + + for (var i = 0; i < 4; i++) + { + num |= message[offset + i] << (8 * (3 - i)); + } + + return num; + }; + + /** + * Write a 64-bit integer to a binary message at a specific offset. + * + * @param {byte[]} message + * Message bytes. + * @param {int} offset + * The location in the message at which the characters start. + * @param {int} value + * The value to write. + */ + me.writeInt64BinaryMessage = function(message, offset, value) + { + // javascript keeps all numbers as decimal, with: + // - the fraction in bits from 0 to 51 + // - the exponent in bits from 52 to 62 + // - the sign in bit 63 + + // for this reason, use the hex representation to transform it into 2 32bit integer values + // and send the high and low bits separately. + var hex = value.toString(16); + + // left-pad with zeros + while (hex.length < 16) + { + hex = "0" + hex; + } + + var hexHigh = hex.substring(0, 8); + var hexLow = hex.substring(8, 16); + + var ihigh = parseInt(hexHigh, 16); + var ilow = parseInt(hexLow, 16); + + // clear high bits 20 to 30 (52 to 62, the exponent) + for (var i = 20; i <= 30; i++) + { + ihigh = ihigh & ~(1 << i); + } + + me.writeInt32BinaryMessage(message, offset, ihigh); + offset = offset + 4; + me.writeInt32BinaryMessage(message, offset, ilow); + offset = offset + 4; + } + + /** + * Write a 32-bit integer to a binary message at a specific offset. + * + * @param {byte[]} message + * Message bytes. + * @param {int} offset + * The location in the message at which the characters start. + * @param {int} value + * The value to write. + */ + me.writeInt32BinaryMessage = function(message, offset, value) + { + message[offset] = ((value >> 24) & 0xFF); + message[offset + 1] = ((value >> 16) & 0xFF); + message[offset + 2] = ((value >> 8) & 0xFF); + message[offset + 3] = ( value & 0xFF); + } + + /** + * Write a 16-bit integer to a binary message at a specific offset. + * + * @param {byte[]} message + * Message bytes. + * @param {int} offset + * The location in the message at which the characters start. + * @param {int} value + * The value to write. + */ + me.writeInt16BinaryMessage = function(message, offset, value) + { + message[offset] = ((value >> 8) & 0xFF); + message[offset + 1] = ( value & 0xFF); + } + + /** + * Read a 16-bit integer from a binary message at a specific offset. + * + * @param {byte[]} message + * Message bytes. + * @param {int} offset + * The location in the message at which the characters start. + * + * @return The number that was read. + */ + me.readInt16BinaryMessage = function(message, offset) + { + var num = 0; + + for (var i = 0; i < 2; i++) + { + num |= message[offset + i] << (8 * (1 - i)); + } + + return num; + }; + + /** + * Parse a hex string and return its byte array. + * + * @param {String} str + * The hex-encoded string. + * + * @return {Uint8Array} byte-array for the given string. + */ + me.hexStringToBytes = function(str) + { + var bytes = new Uint8Array(str.length / 2); + + var idx = 0; + for (var c = 0, idx = 0; c < str.length; c += 2, idx++) + { + bytes[idx] = parseInt(str.substr(c, 2), 16); + } + + return bytes; + } + + /** + * Create a hex string from the given byte array. + * + * @param {Uint8Array} arr + * The byte array. + * + * @return Hex representation of the given byte array. + */ + me.createHexString = function(arr) + { + var result = ""; + for (var i in arr) + { + var str = arr[i].toString(16); + str = str.length == 0 ? "00" : + str.length == 1 ? "0" + str : + str.length == 2 ? str : + str.substring(str.length-2, str.length); + + result += str; + } + + return result; + } + + /** + * Remove the sub-array from the given array and save it in the splice parameter. + * + * @param {array} arr + * The byte array from where elements are removed. + * @param {int} starting + * The starting position (inclusive). + * @param {int} deleteCount + * The number of elements to delete. + * @param {array} splice + * The array where to save the elements (must have enough space). + * + * @return The original array with the elements removed. + */ + me.splice = function(arr, starting, deleteCount, splice) + { + if (arguments.length === 1) + { + return arr; + } + + starting = Math.max(starting, 0); + deleteCount = Math.max(deleteCount, 0); + + const newSize = arr.length - deleteCount; + const splicedArray = new arr.constructor(newSize); + + splicedArray.set(arr.subarray(0, starting)); + splicedArray.set(arr.subarray(starting + deleteCount), starting); + + splice.set(arr.subarray(starting, starting + deleteCount)); + + return splicedArray; + }; + + return me; +} \ No newline at end of file === modified file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js' --- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js 2016-02-03 21:22:52 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js 2016-02-09 20:05:13 +0000 @@ -41,7 +41,9 @@ ** SBI 20160107 Supported new message with 0x9F id to test if the target font family is ** available for the browser. ** CA 20160629 Added a drawing cache (and cache management, when the hash was expired by the -* client-side). +** client-side). +** SBi 20160209 Changed to put the web socket processing in the dedicated web worker thread +** in order to send the large binary messages in packets. */ "use strict"; @@ -66,6 +68,24 @@ /** The count of the drawing operations. */ var drawNo = 0; + /** Web worker */ + var webWorker; + + /** Input and output channel */ + var io = InputOutputChannel( + function(data) + { + if (connected) + { + webWorker.postMessage({type : MSG.MSG_WEB_SOCKET_SEND, message: data}, [data.buffer]); + } + else + { + p2j.sound.beep(); + } + }); + + /** * Send data. * @@ -73,14 +93,7 @@ */ me.send = function(data) { - if (connected) - { - ws.send(data); - } - else - { - p2j.sound.beep(); - } + io.send(data); }; /** @@ -91,10 +104,7 @@ */ me.sendNotification = function(type) { - var msg = new Uint8Array(1); - msg[0] = type; - - me.send(msg); + io.sendNotification(type); }; /** @@ -111,20 +121,7 @@ */ me.sendWindowActive = function(wid, active, focusOut) { - // send the window activation/deactivation to the java side - var msg = new Uint8Array(7); - - // message type - msg[0] = 0x0f; - - // 1. the window ID - me.writeInt32BinaryMessage(msg, 1, wid); - - msg[5] = active ? 1 : 0; - msg[6] = focusOut ? 1 : 0; - - // send the window active event - me.send(msg); + io.sendWindowActive(wid, active, focusOut); } /** @@ -137,19 +134,7 @@ */ me.sendWindowIconState = function(wid, minimized) { - var msg = new Uint8Array(6); - - // message type - msg[0] = 0x10; - - // 0. the window ID - me.writeInt32BinaryMessage(msg, 1, wid); - - // 1. the iconification state - msg[5] = minimized ? 1 : 0; - - // send the mouse event - me.send(msg); + io.sendWindowIconState(wid, minimized); } /** @@ -164,27 +149,7 @@ */ me.sendByteArrayBinaryMessage = function(type, msgId, c) { - var msg = new Uint8Array(1 + 4 + 2 + c.length); - - var offset = 0; - msg[offset] = type; - offset = offset + 1; - - me.writeInt32BinaryMessage(msg, offset, msgId); - offset = offset + 4; - - msg[offset] = c.length & 0xff; - offset = offset + 1; - msg[offset] = (c.length >> 8) & 0xff - offset = offset + 1; - - for (var i = 0; i < c.length; i++) - { - msg[offset] = c[i] & 0xff; - offset = offset + 1; - } - - me.send(msg); + io.sendByteArrayBinaryMessage(type, msgId, c); }; /** @@ -199,13 +164,7 @@ */ me.sendInt8BinaryMessage = function(type, msgId, c) { - var msg = new Uint8Array(1 + 4 + 1); - - msg[0] = type; - me.writeInt32BinaryMessage(msg, 1, msgId); - msg[5] = c; - - me.send(msg); + io.sendInt8BinaryMessage(type, msgId, c); }; /** @@ -221,13 +180,7 @@ */ me.sendInt16BinaryMessage = function(type, msgId, c) { - var msg = new Uint8Array(1 + 4 + 2); - - msg[0] = type; - me.writeInt32BinaryMessage(msg, 1, msgId); - me.writeInt16BinaryMessage(msg, 5, c); - - me.send(msg); + io.sendInt16BinaryMessage(type, msgId, c); }; /** @@ -243,13 +196,7 @@ */ me.sendInt32BinaryMessage = function(type, msgId, c) { - var msg = new Uint8Array(1 + 4 + 4); - - msg[0] = type; - me.writeInt32BinaryMessage(msg, 1, msgId); - me.writeInt32BinaryMessage(msg, 5, c); - - me.send(msg); + io.sendInt32BinaryMessage(type, msgId, c); }; /** @@ -262,18 +209,7 @@ */ me.sendStringBinaryMessage = function(type, text) { - var msg = new Uint8Array(text.length * 2 + 1); - msg[0] = type; - - for (var i = 0, j = 1; i < text.length; i++, j += 2) - { - var c = text.charCodeAt(i); - msg[j + 1] = c & 0xff; - c = c >> 8; - msg[j] = c & 0xff; - } - - me.send(msg); + io.sendStringBinaryMessage(type, text); }; /** @@ -286,16 +222,7 @@ */ me.readStringBinaryMessage = function(message, offset) { - var text = ""; - - for (var i = offset; i < message.length; i += 2) - { - var c = String.fromCharCode((message[i] << 8) | message[i + 1]); - - text = text + c; - } - - return text; + return io.readStringBinaryMessage(message, offset); }; /** @@ -311,16 +238,7 @@ */ me.readStringBinaryMessageByLength = function(message, offset, len) { - var text = ""; - - for (var i = offset; i < offset + len * 2; i += 2) - { - var c = String.fromCharCode((message[i] << 8) | message[i + 1]); - - text = text + c; - } - - return text; + return io.readStringBinaryMessageByLength(message, offset, len); }; /** @@ -335,14 +253,7 @@ */ me.readInt32BinaryMessage = function(message, offset) { - var num = 0; - - for (var i = 0; i < 4; i++) - { - num |= message[offset + i] << (8 * (3 - i)); - } - - return num; + return io.readInt32BinaryMessage(message, offset) }; /** @@ -357,37 +268,7 @@ */ me.writeInt64BinaryMessage = function(message, offset, value) { - // javascript keeps all numbers as decimal, with: - // - the fraction in bits from 0 to 51 - // - the exponent in bits from 52 to 62 - // - the sign in bit 63 - - // for this reason, use the hex representation to transform it into 2 32bit integer values - // and send the high and low bits separately. - var hex = value.toString(16); - - // left-pad with zeros - while (hex.length < 16) - { - hex = "0" + hex; - } - - var hexHigh = hex.substring(0, 8); - var hexLow = hex.substring(8, 16); - - var ihigh = parseInt(hexHigh, 16); - var ilow = parseInt(hexLow, 16); - - // clear high bits 20 to 30 (52 to 62, the exponent) - for (var i = 20; i <= 30; i++) - { - ihigh = ihigh & ~(1 << i); - } - - me.writeInt32BinaryMessage(message, offset, ihigh); - offset = offset + 4; - me.writeInt32BinaryMessage(message, offset, ilow); - offset = offset + 4; + io.writeInt64BinaryMessage(message, offset, value); } /** @@ -402,10 +283,7 @@ */ me.writeInt32BinaryMessage = function(message, offset, value) { - message[offset] = ((value >> 24) & 0xFF); - message[offset + 1] = ((value >> 16) & 0xFF); - message[offset + 2] = ((value >> 8) & 0xFF); - message[offset + 3] = ( value & 0xFF); + io.writeInt32BinaryMessage(message, offset, value); } /** @@ -420,8 +298,7 @@ */ me.writeInt16BinaryMessage = function(message, offset, value) { - message[offset] = ((value >> 8) & 0xFF); - message[offset + 1] = ( value & 0xFF); + io.writeInt16BinaryMessage(message, offset, value); } /** @@ -436,15 +313,271 @@ */ me.readInt16BinaryMessage = function(message, offset) { - var num = 0; + return io.readInt16BinaryMessage(message, offset); + }; + + /** Web worker message handler */ + var messageHandler = function(message) + { + callNo = callNo + 1; + console.log(callNo + ": " + message.type.toString(16)); - for (var i = 0; i < 2; i++) + switch (message.type) { - num |= message[offset + i] << (8 * (1 - i)); - } - - return num; - }; + case MSG.MSG_CLEAR: + // clear screen + p2j.screen.clear(); + break; + case MSG.MSG_DRAW: + drawNo = drawNo + 1; + // draw screen + var t1 = (new Date()).getTime(); + console.debug("drawBuffer.length=" + message.drawBuffer.byteLength); + var binaries = new Uint8Array(message.drawBuffer); + p2j.screen.drawRectangles(binaries, message.md5); + var t2 = (new Date()).getTime(); + console.log(callNo + ":" + drawNo + " draw: " + message.drawBuffer.byteLength + + " done in " + (t2 - t1) + " with md5: " + message.md5); + break; + case MSG.MSG_CURSOR_POS: + // set cursor position + p2j.screen.setCursorPosition(message.col, message.row); + break; + case MSG.MSG_CURSOR_STATUS: + // show cursor + p2j.screen.setCursorStatus(message.status); + break; + case MSG.MSG_BEEP: + // message beep + p2j.sound.beep(); + break; + case MSG.MSG_QUIT: + // quit + window.location.replace(referrer); + break; + case MSG.MSG_VT100: + // switch mode p2j/vt100 + p2j.keyboard.vt100 = (message[1] == 0) ? false : true; + break; + case MSG.MSG_READ_CLIPBOARD: + // server-driven request for clipboard contents + p2j.clipboard.sendClipboardContents(); + break; + case MSG.MSG_WRITE_CLIPBOARD: + // The clipboard is changed. + p2j.clipboard.writeClipboard(message.clipboardText); + break; + case MSG.MSG_CREATE_WINDOW: + // create a top-level window with the given id + p2j.screen.createWindow(message.id); + break; + case MSG.MSG_CREATE_CHILD_WINDOW: + // create a child window with the given id, owner and title + p2j.screen.createChildWindow(message.id, message.owner); + break; + case MSG.MSG_DESTROY_WINDOW: + // destroy top-level or child window + var imagesView = new Int32Array(message.images); + p2j.screen.destroyWindow(message.id, imagesView); + break; + case MSG.MSG_WINDOW_VISIBILITY: + // change visibility for top-level or child window + p2j.screen.setWindowVisible(message.id, message.visible); + break; + // font and metrics related requests + case MSG.MSG_GET_PAR_HEIGHT: + // paragraph height + var pheight = p2j.screen.layoutParagraphWorker(null, + message.text, + message.font, + 0, + 0, + message.maxWidth); + + me.sendInt16BinaryMessage(MSG.MSG_SENT_PAR_HEIGHT, message.msgId, pheight); + break; + case MSG.MSG_GET_TEXT_HEIGHT: + // text height + var theight = p2j.fonts.getTextHeight(message.font, message.text); + + me.sendInt8BinaryMessage(MSG.MSG_SENT_TEXT_HEIGHT, message.msgId, theight); + break; + case MSG.MSG_GET_TEXT_WIDTH: + // text width + var twidth = p2j.fonts.getTextWidth(message.font, message.text); + + me.sendInt16BinaryMessage(MSG.MSG_SENT_TEXT_WIDTH, message.msgId, twidth); + break; + case MSG.MSG_GET_FONT_HEIGHT: + // font height + var fheight = p2j.fonts.getFontHeight(message.font); + + me.sendInt8BinaryMessage(MSG.MSG_SENT_FONT_HEIGHT, message.msgId, fheight); + break; + case MSG.MSG_GET_FONT_WIDTHS: + // font widths + var fwidths = p2j.fonts.getFontWidths(message.font); + + me.sendByteArrayBinaryMessage(MSG.MSG_SENT_FONT_WIDTHS, message.msgId, fwidths); + break; + case MSG.MSG_CREATE_FONT: + // create font + var fontId = p2j.fonts.createFont(message.font, message.name, message.size, + message.style, message.b64font); + + me.sendInt32BinaryMessage(MSG.MSG_DONE_CREATE_FONT, message.msgId, fontId); + break; + case MSG.MSG_DERIVE_FONT: + // derive font + p2j.fonts.deriveFont(message.font); + + me.sendInt8BinaryMessage(MSG.MSG_DONE_DERIVE_FONT, message.msgId, 1); + break; + case MSG.MSG_SET_CURSOR_STYLE: + // set cursor style + p2j.screen.setCursorStyle(message.styleId, message.wid); + break; + case MSG.MSG_RESTACK_WINDOWS: + // restack windows + var winidsView = new Int32Array(message.winids); + p2j.screen.restackZOrderEntries(winidsView); + break; + case MSG.PROCESS_MOUSE_WIDGETS: + // register/deregister widgets for mouse actions + if (!p2j.screen.isExistingWindowId(message.windowID)) + { + console.log("undefined window " + message.windowID); + break; + } + var theWindow = p2j.screen.getWindow(message.windowID); + var widgetsView = new Int32Array(message.widgets); + + switch(message.label) + { + case "registerWidgets": + var actionsView = new Int32Array(message.actions); + var xCoordsView = new Int16Array(message.xCoords); + var yCoordsView = new Int16Array(message.yCoords); + var widthsView = new Int16Array(message.widths); + var heightsView = new Int16Array(message.heights); + for (var k = 0; k < message.widgetNo; k++) + { + theWindow.deregisterMouseWidget(widgetsView[k]); + theWindow.registerMouseWidget( + widgetsView[k], xCoordsView[k], yCoordsView[k], + widthsView[k], heightsView[k], actionsView[k]); + } + var zOrderView = new Int32Array(message.zOrder); + theWindow.setWidgetZOrder(Array.prototype.slice.call(zOrderView)); + break; + case "registerAnyWidgets": + for (var k = 0; k < message.widgetNo; k++) + { + theWindow.deregisterAnyMouseWidget(widgetsView[k]); + theWindow.registerAnyMouseWidget(widgetsView[k]); + } + break; + case "derigisterWidgets": + for (var k = 0; k < message.widgetNo; k++) + { + theWindow.deregisterMouseWidget(widgetsView[k]); + } + break; + case "derigisterAnyWidgets": + for (var k = 0; k < message.widgetNo; k++) + { + theWindow.deregisterAnyMouseWidget(widgetsView[k]); + } + break; + } + break; + case MSG.CAPTURE_MOUSE: + // enable/disable mouse events + p2j.screen.captureMouseEvents(message.capture); + break; + case MSG.ENABLE_OS_EVENTS: + // enable/disable OS events + p2j.screen.enableOsEvents(message.windowID, message.enable); + break; + case MSG.SET_ICONIFICATION_STATE: + var theWindow = p2j.screen.getWindow(message.windowID); + //p2j.logger.log("recieved 0x99: iconified = " + message.iconified + " windowId=" + message.windowID); + if (message.iconified) + { + theWindow.iconify(); + } + else + { + theWindow.deiconify(); + } + break; + case MSG.SET_RESIZEABLE_WINDOW: + // resizeable window + var theWindow = p2j.screen.getWindow(message.windowID); + + theWindow.resizeable = message.resizeable; + + if (message.resizeable) + { + theWindow.minWidth = message.minWidth; + theWindow.minHeight = message.minHeight; + theWindow.maxWidth = message.maxWidth; + theWindow.maxHeight = message.maxHeight; + } + else + { + var rect = theWindow.canvas.getBoundingClientRect(); + + var rwidth = rect.right - rect.left + 1; + var rheight = rect.bottom - rect.top + 1; + + theWindow.minWidth = rwidth; + theWindow.minHeight = rheight; + + theWindow.maxWidth = rwidth; + theWindow.maxHeight = rheight; + } + + break; + case MSG.MSG_CURRENT_SELECTION: + // current editors selection is changed + p2j.clipboard.setSelection(message.clipboardText); + break; + case MSG.MSG_MOVE_TO_TOP: + p2j.screen.moveToTop(message.windowID); + break; + case MSG.MSG_MOVE_TO_BOTTOM: + p2j.screen.moveToBottom(message.windowID); + break; + case MSG.MSG_WINDOW_SENSITIVITY: + // change sensitivity for top-level or child window + p2j.screen.setWindowEnabled(message.windowID, message.enabled); + break; + case MSG.MSG_IS_FONT_INSTALLED: + // font is installed + var result = p2j.fonts.isFontInstalled(message.fontName); + + me.sendInt8BinaryMessage(MSG.MSG_IS_FONT_INSTALLED, message.msgId, result ? 1 : 0); + break; + case MSG.MSG_REMOVE_EXPIRED_HASH: + // remove hashes + var theWindow = p2j.screen.getWindow(message.windowID); + var hashes = new Uint8Array(message.hashes); + var offset = 0; + for (var i = 0; i < message.hashNo; i++) + { + // read each hash and remove it + var hashBytes = hashes.slice(offset, offset + 16); + var hash = io.createHexString(hashBytes); + delete theWindow.removeCachedDraw(hash); + + offset += 16; + } + + me.sendInt8BinaryMessage(MSG.MSG_DRAW_HASH_REMOVED, message.msgId, 1); + break; + }; + } /** * Initialize module. @@ -454,639 +587,77 @@ me.init = function(cfg) { referrer = cfg.referrer; + + webWorker = new Worker("/common/p2j.connector.js"); - if ('WebSocket' in window) + /** + * Handles messages from the dedicated web worker. + * + * @param {MessageEvent} evt + * The message from the web worker. + */ + var webWorkerHandler = function(evt) { - ws = new WebSocket(cfg.socket.url); - - // on web socket open - ws.onopen = function() - { - connected = true; - ws.binaryType = 'arraybuffer'; - - // notify the web socket has opened and the page is loaded - me.sendNotification(0xff); - }; - - /** Web socket message handler */ - var messageHandler = function(message) - { - callNo = callNo + 1; - console.log(callNo + ": " + message[0].toString(16)); - - switch (message[0]) - { - case 0x80: - // clear screen - p2j.screen.clear(); - break; - case 0x81: - // remove the MD5 code and save it here - var md5a = new message.constructor(16); - message = splice(message, 1, 16, md5a); - var md5 = me.createHexString(md5a); - - drawNo = drawNo + 1; - // draw screen - var t1 = (new Date()).getTime(); - p2j.screen.drawRectangles(message, md5); - var t2 = (new Date()).getTime(); - console.log(callNo + ":" + drawNo + " draw: " + message.length + " done in " + (t2 - t1) + " with md5: " + md5); - break; - case 0x82: - // set cursor position - p2j.screen.setCursorPosition(message[1], message[2]); - break; - case 0x83: - // show cursor - p2j.screen.setCursorStatus(message[1]); - break; - case 0x84: - // message beep - p2j.sound.beep(); - break; - case 0x85: - // quit - window.location.replace(referrer); - break; - case 0x86: - // switch mode p2j/vt100 - p2j.keyboard.vt100 = (message[1] == 0) ? false : true; - break; - case 0x87: - // server-driven request for clipboard contents - p2j.clipboard.sendClipboardContents(); - break; - case 0x88: - // The clipboard is changed. - var text = me.readStringBinaryMessage(message, 1); - p2j.clipboard.writeClipboard(text); - break; - case 0x89: - // create a top-level window with the given id - var id = me.readInt32BinaryMessage(message, 1); - p2j.screen.createWindow(id); - break; - case 0x8A: - // create a child window with the given id, owner and title - var id = me.readInt32BinaryMessage(message, 1); - var owner = me.readInt32BinaryMessage(message, 5); - var title = me.readStringBinaryMessage(message, 9); - p2j.screen.createChildWindow(id, owner); - break; - case 0x8B: - // destroy top-level or child window - var id = me.readInt32BinaryMessage(message, 1); - var numberImages = me.readInt32BinaryMessage(message, 5); - var images = []; - for (var i = 0; i < numberImages; i++) - { - images[i] = me.readInt32BinaryMessage(message, 9 + (i * 4)); - } - p2j.screen.destroyWindow(id, images); - break; - case 0x8C: - // change visibility for top-level or child window - var id = me.readInt32BinaryMessage(message, 1); - var visible = message[5] === 0 ? false : true; - p2j.screen.setWindowVisible(id, visible); - break; - - // font and metrics related requests - case 0x8D: - // paragraph height - - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var textLength = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var text = me.readStringBinaryMessageByLength(message, offset, textLength); - offset = offset + textLength * 2; - - var font = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var maxWidth = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - var pheight = p2j.screen.layoutParagraphWorker(null, - text, - font, - 0, - 0, - maxWidth); - - me.sendInt16BinaryMessage(0x06, msgId, pheight); - break; - case 0x8E: - // text height - - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var textLength = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var text = me.readStringBinaryMessageByLength(message, offset, textLength); - offset = offset + textLength * 2; - - var font = me.readInt32BinaryMessage(message, offset); - - var theight = p2j.fonts.getTextHeight(font, text); - - me.sendInt8BinaryMessage(0x07, msgId, theight); - break; - case 0x8F: - // text width - - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var textLength = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var text = me.readStringBinaryMessageByLength(message, offset, textLength); - offset = offset + textLength * 2; - - var font = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var twidth = p2j.fonts.getTextWidth(font, text); - - me.sendInt16BinaryMessage(0x08, msgId, twidth); - break; - case 0x90: - // font height - - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var font = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var fheight = p2j.fonts.getFontHeight(font); - - me.sendInt8BinaryMessage(0x09, msgId, fheight); - break; - case 0x91: - // font widths - - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var font = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var fwidths = p2j.fonts.getFontWidths(font); - - me.sendByteArrayBinaryMessage(0x0A, msgId, fwidths); - break; - case 0x92: - // create font - - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var font = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var nameLength = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - var name = me.readStringBinaryMessageByLength(message, offset, nameLength); - offset = offset + nameLength * 2; - - var size = message[offset]; - offset = offset + 1; - - var style = message[offset]; - offset = offset + 1; - - var defLength = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var b64font = ""; - if (defLength > 0) - { - var binFont = ''; - for (var i = 0; i < defLength; i++) - { - binFont += String.fromCharCode(message[offset]); - offset = offset + 1; - } - b64font = window.btoa(binFont); - } - - var fontId = p2j.fonts.createFont(font, name, size, style, b64font); - - me.sendInt32BinaryMessage(0x0B, msgId, fontId); - break; - case 0x93: - // derive font - - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var font = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - p2j.fonts.deriveFont(font); - - me.sendInt8BinaryMessage(0x0C, msgId, 1); - break; - case 0x94: - // set cursor style - var styleId = me.readInt32BinaryMessage(message, 1); - var wid = me.readInt32BinaryMessage(message, 5); - p2j.screen.setCursorStyle(styleId, wid); - break; - case 0x95: - // restack windows - var num = me.readInt32BinaryMessage(message, 1); - var winids = []; - for (var i = 0; i < num; i++) - { - winids.push(me.readInt32BinaryMessage(message, 5 + (i * 4))); - } - p2j.screen.restackZOrderEntries(winids); - break; - case 0x96: - // register/deregister widgets for mouse actions - - var offset = 1; - - // the number of windows with new widgets - var windowNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var j = 0; j < windowNo; j++) - { - // the window ID - var windowID = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - if (!p2j.screen.isExistingWindowId(windowID)) - { - console.log("undefined window " + windowID); - continue; - } - - var theWindow = p2j.screen.getWindow(windowID); - - // the number of new widgets in this window - var widgetNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var k = 0; k < widgetNo; k++) - { - // the widget ID - var wid = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - // the coordinates - var x = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - var y = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - // the size - var width = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - var height = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - // bit-encoded mouse ops: each bit from 1 to 11, if set, represents a mouse - // operation as defined in p2j.screen.mouseOps - var actions = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - theWindow.deregisterMouseWidget(wid); - theWindow.registerMouseWidget(wid, x, y, width, height, actions); - } - - var allWNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - var zOrder = []; - - for (var k = 0; k < allWNo; k++) - { - zOrder[k] = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - } - - theWindow.setWidgetZOrder(zOrder); - } - - // the number of windows with dead widgets - windowNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var j = 0; j < windowNo; j++) - { - // the window ID - var windowID = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var theWindow = p2j.screen.getWindow(windowID); - - if (theWindow == undefined) - { - console.log("undefined window" + windowID); - continue; - } - - // the number of dead widgets in this window - var widgetNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var k = 0; k < widgetNo; k++) - { - // the widget ID - var wid = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - theWindow.deregisterMouseWidget(wid); - } - } - - // the number of windows with new "any widgets" - windowNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var j = 0; j < windowNo; j++) - { - // the window ID - var windowID = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - if (!p2j.screen.isExistingWindowId(windowID)) - { - console.log("undefined window " + windowID); - continue; - } - - var theWindow = p2j.screen.getWindow(windowID); - - // the number of new "any widgets" in this window - var widgetNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var k = 0; k < widgetNo; k++) - { - // the widget ID - var wid = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - theWindow.deregisterAnyMouseWidget(wid); - theWindow.registerAnyMouseWidget(wid); - } - } - - // the number of windows with dead "any widgets" - windowNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var j = 0; j < windowNo; j++) - { - // the window ID - var windowID = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var theWindow = p2j.screen.getWindow(windowID); - - if (theWindow == undefined) - { - console.log("undefined window" + windowID); - continue; - } - - // the number of dead "any widgets" in this window - var widgetNo = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - for (var k = 0; k < widgetNo; k++) - { - // the widget ID - var wid = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - theWindow.deregisterAnyMouseWidget(wid); - } - } - - break; - case 0x97: - // enable/disable mouse events - p2j.screen.captureMouseEvents(message[1] == 1); - break; - case 0x98: - // enable/disable OS events - var wid = me.readInt32BinaryMessage(message, 1); - p2j.screen.enableOsEvents(wid, message[5] == 1); - break; - case 0x99: - // set window iconification state - var offset = 1; - - var windowID = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var iconified = (message[offset] == 1); - offset = offset + 1; - - var theWindow = p2j.screen.getWindow(windowID); - //p2j.logger.log("recieved 0x99: iconified = " + iconified + " windowId=" + windowID); - if (iconified) - { - theWindow.iconify(); - } - else - { - theWindow.deiconify(); - } - - break; - case 0x9A: - // resizeable window - var offset = 1; - - var windowID = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var theWindow = p2j.screen.getWindow(windowID); - - theWindow.resizeable = (message[offset] == 1); - offset = offset + 1; - - if (theWindow.resizeable) - { - theWindow.minWidth = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - theWindow.minHeight = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - - theWindow.maxWidth = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - theWindow.maxHeight = me.readInt16BinaryMessage(message, offset); - offset = offset + 2; - } - else - { - var rect = theWindow.canvas.getBoundingClientRect(); - - var rwidth = rect.right - rect.left + 1; - var rheight = rect.bottom - rect.top + 1; - - theWindow.minWidth = rwidth; - theWindow.minHeight = rheight; - - theWindow.maxWidth = rwidth; - theWindow.maxHeight = rheight; - } - - break; - case 0x9B: - // current editors selection is changed - var text = me.readStringBinaryMessage(message, 1); - p2j.clipboard.setSelection(text); - break; - case 0x9C: - var id = me.readInt32BinaryMessage(message, 1); - p2j.screen.moveToTop(id); - break; - case 0x9D: - var id = me.readInt32BinaryMessage(message, 1); - p2j.screen.moveToBottom(id); - break; - case 0x9E: - // change sensitivity for top-level or child window - var id = me.readInt32BinaryMessage(message, 1); - var enabled = message[5] === 0 ? false : true; - p2j.screen.setWindowEnabled(id, enabled); - break; - case 0x9F: - // font is installed - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var fontNameLength = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var fontName = me.readStringBinaryMessageByLength(message, offset, fontNameLength); - - var result = p2j.fonts.isFontInstalled(fontName); - - me.sendInt8BinaryMessage(0x9F, msgId, result ? 1 : 0); - break; - case 0xA0: - // remove hashes - var offset = 1; - - var msgId = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - // window ID - var windowID = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - // number of hashes - var hashNo = me.readInt32BinaryMessage(message, offset); - offset = offset + 4; - - var theWindow = p2j.screen.getWindow(windowID); - - for (var i = 0; i < hashNo; i++) - { - // read each hash and remove it - var hashBytes = message.slice(offset, offset + 16); - var hash = me.createHexString(hashBytes); - delete theWindow.removeCachedDraw(hash); - - offset += 16; - } - - me.sendInt8BinaryMessage(0x12, msgId, 1); - break; - }; - }; - // on web socket message - ws.onmessage = function(evt) - { - var data = evt.data; - - if (data instanceof ArrayBuffer) - { - // binary messages - var message = new Uint8Array(data); - var isDrawing = message[0] === 0x81; - if (isDrawing) - { - p2j.displayWaitCursor(); - } - setTimeout(function() { messageHandler(message); }, 1); - if (isDrawing) - { - setTimeout(function() { p2j.restoreCursor(); }, 1); - } - } - else - { - // text messages - var pay = JSON.parse(data); + var data = evt.data; + switch(data.type) + { + case MSG.MSG_WEB_SOCKET_ERROR: + p2j.screen.error('WebSockets are NOT supported by your Browser.'); + webWorker.terminate(); + break; + case MSG.MSG_COLOR_PALETTE: setTimeout( function() { - switch (pay.c) + switch (data.msg.c) { case 0: // color palette - p2j.screen.palette = pay.p; + p2j.screen.palette = data.msg.p; break; }; }, 1); - } + break; + case MSG.MSG_WEB_SOCKET_OPEN: + // on web socket open + connected = true; + webWorker.postMessage({type: MSG.MSG_PAGE_LOADED}); + break; + case MSG.MSG_WEB_SOCKET_CLOSE: + // on web socket close + connected = false; + p2j.screen.error('Connection closed by remote host.'); + window.setInterval(function() { window.location.replace(referrer); }, 5000); + break; + case MSG.MSG_DRAW: + p2j.displayWaitCursor(); + setTimeout( + function() + { + messageHandler(data); + }, 1); + setTimeout(function() { p2j.restoreCursor(); }, 1); + break; + default: + setTimeout( + function() + { + messageHandler(data); + }, 1); } - - // on web socket close - ws.onclose = function() - { - connected = false; - p2j.screen.error('Connection closed by remote host.'); - window.setInterval(function() { window.location.replace(referrer); }, 5000); - }; - - // refresh - window.onpagehide = function() - { - if (connected) - { - ws.close(); - } - }; - } - else + }; + + /** Added the web worker message listener */ + webWorker.addEventListener("message", webWorkerHandler); + + // Directs the web worker to open a web socket connection + webWorker.postMessage({type: MSG.MSG_WEB_SOCKET_OPEN, url : cfg.socket.url}); + + // refresh + window.onpagehide = function() { - p2j.screen.error('WebSockets are NOT supported by your Browser.'); - } + webWorker.postMessage({type: MSG.MSG_WEB_SOCKET_CLOSE}); + }; }; /** @@ -1099,15 +670,7 @@ */ me.hexStringToBytes = function(str) { - var bytes = new Uint8Array(str.length / 2); - - var idx = 0; - for (var c = 0, idx = 0; c < str.length; c += 2, idx++) - { - bytes[idx] = parseInt(str.substr(c, 2), 16); - } - - return bytes; + return io.hexStringToBytes(str); } /** @@ -1120,55 +683,8 @@ */ me.createHexString = function(arr) { - var result = ""; - for (var i in arr) - { - var str = arr[i].toString(16); - str = str.length == 0 ? "00" : - str.length == 1 ? "0" + str : - str.length == 2 ? str : - str.substring(str.length-2, str.length); - - result += str; - } - - return result; + return io.createHexString(arr); } - /** - * Remove the sub-array from the given array and save it in the splice parameter. - * - * @param {array} arr - * The byte array from where elements are removed. - * @param {int} starting - * The starting position (inclusive). - * @param {int} deleteCount - * The number of elements to delete. - * @param {array} splice - * The array where to save the elements (must have enough space). - * - * @return The original array with the elements removed. - */ - function splice(arr, starting, deleteCount, splice) - { - if (arguments.length === 1) - { - return arr; - } - - starting = Math.max(starting, 0); - deleteCount = Math.max(deleteCount, 0); - - const newSize = arr.length - deleteCount; - const splicedArray = new arr.constructor(newSize); - - splicedArray.set(arr.subarray(0, starting)); - splicedArray.set(arr.subarray(starting + deleteCount), starting); - - splice.set(arr.subarray(starting, starting + deleteCount)); - - return splicedArray; - }; - return me; }());