Project

General

Profile

2968_1.txt

Sergey Ivanovskiy, 02/24/2016 05:55 PM

Download (62.5 KB)

 
1
=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/WebClientMessageTypes.java'
2
--- src/com/goldencode/p2j/ui/client/driver/web/WebClientMessageTypes.java	2016-02-23 16:33:44 +0000
3
+++ src/com/goldencode/p2j/ui/client/driver/web/WebClientMessageTypes.java	2016-02-24 22:37:36 +0000
4
@@ -19,7 +19,7 @@
5
 **                  and z-order operations.
6
 ** 005 CA  20151024 Added support for WINDOW:SENSITIVE attribute.
7
 ** 006 CA  20160203 Added hash management operations.
8
-**     SBI 20160220 Added MSG_PARTIAL, MSG_FILE_UPLOAD
9
+**     SBI 20160225 Added MSG_PARTIAL, MSG_PING_PONG
10
 */
11
 
12
 package com.goldencode.p2j.ui.client.driver.web;
13
@@ -187,5 +187,8 @@
14
 
15
    /** Wraps the large message from the client to the sequence of this partial type messages. */
16
    public static final byte MSG_PARTIAL = (byte) 0xFE;
17
-
18
+   
19
+   /** Ping/Pong messages. */
20
+   public static final byte MSG_PING_PONG = (byte) 0xFC;
21
+   
22
 }
23

    
24
=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java'
25
--- src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java	2016-02-24 06:12:51 +0000
26
+++ src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java	2016-02-24 22:38:20 +0000
27
@@ -19,7 +19,7 @@
28
 **                  message result.
29
 ** 005 CA  20160203 Fixed deadlock for receivedMessages - it must lock on the "lock" field, as
30
 **                  this object is used for all sock operations (including key reading).
31
-**     SBI 20160220 Added MSG_PARTIAL and MSG_FILE_UPLOAD.
32
+**     SBI 20160225 Added MSG_PARTIAL and MSG_PING_PONG.
33
 */
34
 
35
 package com.goldencode.p2j.ui.client.driver.web;
36
@@ -374,6 +374,11 @@
37
          getMessagesCollector().processPartialMesssage(message, offset);
38
          handled = true;
39
       }
40
+      else if (message[offset] == MSG_PING_PONG && length == 1)
41
+      {
42
+         sendBinaryMessage(MSG_PING_PONG);
43
+         handled = true;
44
+      }
45
       
46
       return handled;
47
    }
48
@@ -413,7 +418,11 @@
49
    {
50
       synchronized (lock)
51
       {
52
-         session.close();      
53
+         if (error != null)
54
+         {
55
+            error.printStackTrace();
56
+         }
57
+         session.close(StatusCode.ABNORMAL, "");
58
       }
59
    }
60
 
61

    
62
=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/WebPageHandler.java'
63
--- src/com/goldencode/p2j/ui/client/driver/web/WebPageHandler.java	2016-02-20 18:15:08 +0000
64
+++ src/com/goldencode/p2j/ui/client/driver/web/WebPageHandler.java	2016-02-24 22:40:56 +0000
65
@@ -12,8 +12,8 @@
66
 ** 001 GES 20150312 Created initial version by moving code from the ChuiWebPageHandler and making
67
 **                  it more generic (to handle GUI too).
68
 ** 002 GES 20150717 Moved clipboard processing to ChUI-specific location.
69
-** 003 SBI 20160220 Added maxBinaryMessage parameter to the template file in order to deliver
70
-**                  its value to the JS client.
71
+** 003 SBI 20160225 Added "maxBinaryMessage", "watchdogTimeout" and "maxIdleTime" parameters to
72
+**                  the template file in order to deliver its value to the JS client.
73
 */
74
 
75
 package com.goldencode.p2j.ui.client.driver.web;
76
@@ -158,6 +158,31 @@
77
          text = text.replace("${maxBinaryMessage}", String.valueOf(maxBinaryMessage));
78
       }
79
       
80
+      if (text.contains("${maxIdleTime}"))
81
+      {
82
+         int socketTimeout = config.getInt("client", "web", "socketTimeout", -1);
83
+         int maxIdleTime;
84
+         // if socketTimeout > 0, then it overrides maxIdleTime
85
+         // at the start of the websocket session.
86
+         if (socketTimeout > 0)
87
+         {
88
+            maxIdleTime = socketTimeout;
89
+         }
90
+         else
91
+         {
92
+            // configuration must provide the valid value of client:web:maxIdleTime
93
+            maxIdleTime = config.getInt("client", "web", "maxIdleTime", -1);
94
+         }
95
+         text = text.replace("${maxIdleTime}", String.valueOf(maxIdleTime));
96
+      }
97
+      
98
+      if (text.contains("${watchdogTimeout}"))
99
+      {
100
+         // a watch dog checks the elapsed time from its start one per a minute
101
+         int watchdogTimeout = config.getInt("client", "web", "watchdogTimeout", 60000);
102
+         text = text.replace("${watchdogTimeout}", String.valueOf(watchdogTimeout));
103
+      }
104
+      
105
       return text;
106
    }
107
    
108

    
109
=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/index.html'
110
--- src/com/goldencode/p2j/ui/client/driver/web/index.html	2016-02-20 18:15:08 +0000
111
+++ src/com/goldencode/p2j/ui/client/driver/web/index.html	2016-02-24 13:43:13 +0000
112
@@ -33,16 +33,36 @@
113
             "use strict";
114
             
115
             p2j.init({
116
-               'isGui' : ${isGui},
117
-               'canvas' : {'size' : {'${height_name}' : ${height_val}, '${width_name}' : ${width_val}}, 'mouse' : false},
118
-               'font' : {'name' : '${font.name}', 'size' : ${font.size}, 'color' : {'f' : '${font.color}', 'b' : '${font.background}'}},
119
-               'cursor' : {'type' : 'solid', 'visible' : true, 'blinking' : false},
120
-               'sound' : {'id' : 'beep', 'enabled' : true},
121
-               'socket' : {'url' : 'wss://' + window.location.host + '${context}/ajax', 'maxBinaryMessage' : ${maxBinaryMessage}},
122
-               'page' : 'index.html',
123
-               'clipboard' : {'enabled' : ${clipboard.enabled}, 'id' : 'clipboard', 'stroke' : true, 'input' : 'copy'},
124
-               'taskbar' : {'enabled' : ${taskbar.enabled}},
125
-               'referrer' : '${referrer}',
126
+               'isGui'     : ${isGui},
127
+               'canvas'    : {
128
+                              'size' : {
129
+                                       '${height_name}' : ${height_val},
130
+                                       '${width_name}' : ${width_val}
131
+                                       },
132
+                              'mouse' : false
133
+                              },
134
+               'font'      : {
135
+                              'name' : '${font.name}',
136
+                              'size' : ${font.size},
137
+                              'color' : {'f' : '${font.color}', 'b' : '${font.background}'}
138
+                              },
139
+               'cursor'    : {'type' : 'solid', 'visible' : true, 'blinking' : false},
140
+               'sound'     : {'id' : 'beep', 'enabled' : true},
141
+               'socket'    : {
142
+                              'url' : 'wss://' + window.location.host + '${context}/ajax',
143
+                              'maxBinaryMessage' : ${maxBinaryMessage},
144
+                              'maxIdleTime'      : ${maxIdleTime},
145
+                              'watchdogTimeout'  : ${watchdogTimeout}
146
+                              },
147
+               'page'      : 'index.html',
148
+               'clipboard' : {
149
+                              'enabled' : ${clipboard.enabled},
150
+                              'id'      : 'clipboard',
151
+                              'stroke'  : true,
152
+                              'input'   : 'copy'
153
+                              },
154
+               'taskbar'   : {'enabled' : ${taskbar.enabled}},
155
+               'referrer'  : '${referrer}',
156
                'container' : 'cont'
157
             });
158
          });
159

    
160
=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js'
161
--- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js	2016-02-24 06:12:51 +0000
162
+++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js	2016-02-24 22:36:46 +0000
163
@@ -42,7 +42,7 @@
164
 **                  available for the browser.
165
 **     CA  20160629 Added a drawing cache (and cache management, when the hash was expired by the
166
 **                  client-side).
167
-**     SBI 20160202 Implemented MSG_PARTIAL.
168
+**     SBI 20160225 Implemented MSG_PARTIAL, added MSG_PING_PONG and idle/connectivity timers.
169
 */
170
 
171
 "use strict";
172
@@ -70,6 +70,27 @@
173
    /** The maximal size for binary messages that can be accepted by the websocket server. */ 
174
    var maxBinaryMessage;
175
    
176
+   /** The maximal idle time for this websocket connection */
177
+   var maxIdleTime;
178
+   
179
+   /**
180
+    * The maximal period within which to establish new connections with the same websocket server
181
+    * instance is possible.
182
+    */
183
+   var watchdogTimeout;
184
+   
185
+   /** The websocket connection url */
186
+   var socketUrl;
187
+   
188
+   /** The Idle Timer */
189
+   var idleTimer;
190
+   
191
+   /** The Connectivity Timer */
192
+   var connectivityTimer;
193
+   
194
+   /** Watch Ping/Pong */
195
+   var pingPongWatcher;
196
+   
197
    /**
198
     * The next unique message id.
199
     */
200
@@ -610,6 +631,773 @@
201
       
202
       return num;
203
    };
204
+
205
+   /** Web socket message handler */
206
+   var messageHandler = function(message)
207
+   {
208
+      var t1 = (new Date()).getTime();
209
+      callNo = callNo + 1;
210
+      
211
+      // reset the last activity time
212
+      idleTimer.reset(t1);
213
+      
214
+      switch (message[0])
215
+      {
216
+         case 0x80:
217
+            // clear screen 
218
+            p2j.screen.clear();                        
219
+            break;
220
+         case 0x81:
221
+            p2j.screen.drawRectangles(message);
222
+            var t2 = (new Date()).getTime();
223
+            console.log(callNo + ":" + drawNo + " draw: " + message.length + " done in " + (t2 - t1));
224
+            return;
225
+         case 0x82:
226
+            // set cursor position
227
+            p2j.screen.setCursorPosition(message[1], message[2]);                        
228
+            break;
229
+         case 0x83:
230
+            // show cursor
231
+            p2j.screen.setCursorStatus(message[1]);                        
232
+            break;
233
+         case 0x84:
234
+            // message beep
235
+            p2j.sound.beep();                        
236
+            break;
237
+         case 0x85:
238
+            // quit
239
+            window.location.replace(referrer);                        
240
+            break;
241
+         case 0x86:
242
+            // switch mode p2j/vt100
243
+            p2j.keyboard.vt100 = (message[1] == 0) ? false : true;          
244
+            break;
245
+         case 0x87:
246
+            // server-driven request for clipboard contents
247
+            p2j.clipboard.sendClipboardContents();          
248
+            break;
249
+         case 0x88:
250
+            // The clipboard is changed.
251
+            var text = me.readStringBinaryMessage(message, 1);
252
+            p2j.clipboard.writeClipboard(text);
253
+            break;
254
+         case 0x89:
255
+            // create a top-level window with the given id
256
+            var id = me.readInt32BinaryMessage(message, 1);
257
+            p2j.screen.createWindow(id);
258
+            break;
259
+         case 0x8A:
260
+            // create a child window with the given id, owner and title
261
+            var id    = me.readInt32BinaryMessage(message, 1);
262
+            var owner = me.readInt32BinaryMessage(message, 5);
263
+            var title = me.readStringBinaryMessage(message, 9);
264
+            p2j.screen.createChildWindow(id, owner);
265
+            break;
266
+         case 0x8B:
267
+            // destroy top-level or child window
268
+            var id = me.readInt32BinaryMessage(message, 1);
269
+            var numberImages = me.readInt32BinaryMessage(message, 5);
270
+            var images = [];
271
+            for (var i = 0; i < numberImages; i++)
272
+            {
273
+               images[i] = me.readInt32BinaryMessage(message, 9 + (i * 4));
274
+            }
275
+            p2j.screen.destroyWindow(id, images);
276
+            break;
277
+         case 0x8C:
278
+            // change visibility for top-level or child window
279
+            var id = me.readInt32BinaryMessage(message, 1);
280
+            var visible = message[5] === 0 ? false : true;
281
+            p2j.screen.setWindowVisible(id, visible);
282
+            break;
283
+            
284
+         // font and metrics related requests
285
+         case 0x8D:
286
+            // paragraph height
287
+            
288
+            var offset = 1;
289
+            
290
+            var msgId = me.readInt32BinaryMessage(message, offset);
291
+            offset = offset + 4;
292
+            
293
+            var textLength = me.readInt32BinaryMessage(message, offset);
294
+            offset = offset + 4;
295
+
296
+            var text  = me.readStringBinaryMessageByLength(message, offset, textLength);
297
+            offset = offset + textLength * 2;
298
+            
299
+            var font = me.readInt32BinaryMessage(message, offset);
300
+            offset = offset + 4;
301
+
302
+            var maxWidth = me.readInt16BinaryMessage(message, offset);
303
+            offset = offset + 2;
304
+
305
+            var pheight = p2j.screen.layoutParagraphWorker(null,
306
+                                                           text,
307
+                                                           font, 
308
+                                                           0,
309
+                                                           0, 
310
+                                                           maxWidth);
311
+
312
+            me.sendInt16BinaryMessage(0x06, msgId, pheight);
313
+            break;
314
+         case 0x8E:
315
+            // text height
316
+            
317
+            var offset = 1;
318
+
319
+            var msgId = me.readInt32BinaryMessage(message, offset);
320
+            offset = offset + 4;
321
+            
322
+            var textLength = me.readInt32BinaryMessage(message, offset);
323
+            offset = offset + 4;
324
+
325
+            var text  = me.readStringBinaryMessageByLength(message, offset, textLength);
326
+            offset = offset + textLength * 2;
327
+
328
+            var font = me.readInt32BinaryMessage(message, offset);
329
+            
330
+            var theight = p2j.fonts.getTextHeight(font, text);
331
+
332
+            me.sendInt8BinaryMessage(0x07, msgId, theight);
333
+            break;
334
+         case 0x8F:
335
+            // text width
336
+
337
+            var offset = 1;
338
+            
339
+            var msgId = me.readInt32BinaryMessage(message, offset);
340
+            offset = offset + 4;
341
+            
342
+            var textLength = me.readInt32BinaryMessage(message, offset);
343
+            offset = offset + 4;
344
+
345
+            var text  = me.readStringBinaryMessageByLength(message, offset, textLength);
346
+            offset = offset + textLength * 2;
347
+
348
+            var font = me.readInt32BinaryMessage(message, offset);
349
+            offset = offset + 4;
350
+            
351
+            var twidth = p2j.fonts.getTextWidth(font, text);
352
+            
353
+            me.sendInt16BinaryMessage(0x08, msgId, twidth);
354
+            break;
355
+         case 0x90:
356
+            // font height
357
+
358
+            var offset = 1;
359
+
360
+            var msgId = me.readInt32BinaryMessage(message, offset);
361
+            offset = offset + 4;
362
+
363
+            var font = me.readInt32BinaryMessage(message, offset);
364
+            offset = offset + 4;
365
+            
366
+            var fheight = p2j.fonts.getFontHeight(font);
367
+            
368
+            me.sendInt8BinaryMessage(0x09, msgId, fheight);
369
+            break;
370
+         case 0x91:
371
+            // font widths
372
+
373
+            var offset = 1;
374
+            
375
+            var msgId = me.readInt32BinaryMessage(message, offset);
376
+            offset = offset + 4;
377
+            
378
+            var font = me.readInt32BinaryMessage(message, offset);
379
+            offset = offset + 4;
380
+            
381
+            var fwidths = p2j.fonts.getFontWidths(font);
382
+            
383
+            me.sendByteArrayBinaryMessage(0x0A, msgId, fwidths);
384
+            break;
385
+         case 0x92:
386
+            // create font
387
+
388
+            var offset = 1;
389
+            
390
+            var msgId = me.readInt32BinaryMessage(message, offset);
391
+            offset = offset + 4;
392
+            
393
+            var font = me.readInt32BinaryMessage(message, offset);
394
+            offset = offset + 4;
395
+            
396
+            var nameLength = me.readInt16BinaryMessage(message, offset);
397
+            offset = offset + 2;
398
+
399
+            var name  = me.readStringBinaryMessageByLength(message, offset, nameLength);
400
+            offset = offset + nameLength * 2;
401
+            
402
+            var size = message[offset];
403
+            offset = offset + 1;
404
+            
405
+            var style = message[offset];
406
+            offset = offset + 1;
407
+            
408
+            var defLength = me.readInt32BinaryMessage(message, offset);
409
+            offset = offset + 4;
410
+            
411
+            var b64font = "";
412
+            if (defLength > 0)
413
+            {
414
+               var binFont = '';
415
+               for (var i = 0; i < defLength; i++)
416
+               {
417
+                  binFont += String.fromCharCode(message[offset]);
418
+                  offset = offset + 1;
419
+               }
420
+               b64font = window.btoa(binFont);
421
+            }
422
+
423
+            var fontId = p2j.fonts.createFont(font, name, size, style, b64font);
424
+            
425
+            me.sendInt32BinaryMessage(0x0B, msgId, fontId);
426
+            break;
427
+         case 0x93:
428
+            // derive font
429
+
430
+            var offset = 1;
431
+            
432
+            var msgId = me.readInt32BinaryMessage(message, offset);
433
+            offset = offset + 4;
434
+            
435
+            var font = me.readInt32BinaryMessage(message, offset);
436
+            offset = offset + 4;
437
+
438
+            p2j.fonts.deriveFont(font);
439
+             
440
+            me.sendInt8BinaryMessage(0x0C, msgId, 1);
441
+            break;
442
+         case 0x94:
443
+            // set cursor style
444
+            var styleId = me.readInt32BinaryMessage(message, 1);
445
+            var wid = me.readInt32BinaryMessage(message, 5);
446
+            p2j.screen.setCursorStyle(styleId, wid);
447
+            break;
448
+         case 0x95:
449
+            // restack windows
450
+            var num = me.readInt32BinaryMessage(message, 1);
451
+            var winids = [];
452
+            for (var i = 0; i < num; i++)
453
+            {
454
+               winids.push(me.readInt32BinaryMessage(message, 5 + (i * 4)));
455
+            }
456
+            p2j.screen.restackZOrderEntries(winids);
457
+            break;
458
+         case 0x96:
459
+             // register/deregister widgets for mouse actions
460
+             
461
+             var offset = 1;
462
+             
463
+             // the number of windows with new widgets
464
+             var windowNo = me.readInt16BinaryMessage(message, offset);
465
+             offset = offset + 2;
466
+             
467
+             for (var j = 0; j < windowNo; j++)
468
+             {
469
+                // the window ID
470
+                var windowID = me.readInt32BinaryMessage(message, offset);
471
+                offset = offset + 4;
472
+                
473
+                if (!p2j.screen.isExistingWindowId(windowID))
474
+                {
475
+                   console.log("undefined window " + windowID);
476
+                   continue;
477
+                }
478
+                
479
+                var theWindow = p2j.screen.getWindow(windowID);
480
+                
481
+                // the number of new widgets in this window
482
+                var widgetNo = me.readInt16BinaryMessage(message, offset);
483
+                offset = offset + 2;
484
+                
485
+                for (var k = 0; k < widgetNo; k++)
486
+                {
487
+                   // the widget ID
488
+                   var wid = me.readInt32BinaryMessage(message, offset);
489
+                   offset = offset + 4;
490
+
491
+                   // the coordinates
492
+                   var x = me.readInt16BinaryMessage(message, offset);
493
+                   offset = offset + 2;
494
+                   
495
+                   var y = me.readInt16BinaryMessage(message, offset);
496
+                   offset = offset + 2;
497
+   
498
+                   // the size
499
+                   var width = me.readInt16BinaryMessage(message, offset);
500
+                   offset = offset + 2;
501
+   
502
+                   var height = me.readInt16BinaryMessage(message, offset);
503
+                   offset = offset + 2;
504
+   
505
+                   // bit-encoded mouse ops: each bit from 1 to 11, if set, represents a mouse
506
+                   // operation as defined in p2j.screen.mouseOps
507
+                   var actions = me.readInt32BinaryMessage(message, offset);
508
+                   offset = offset + 4;
509
+                   theWindow.deregisterMouseWidget(wid);
510
+                   theWindow.registerMouseWidget(wid, x, y, width, height, actions);
511
+                }
512
+                
513
+                var allWNo = me.readInt16BinaryMessage(message, offset);
514
+                offset = offset + 2;
515
+                var zOrder = [];
516
+                
517
+                for (var k = 0; k < allWNo; k++)
518
+                {
519
+                  zOrder[k] = me.readInt32BinaryMessage(message, offset);
520
+                  offset = offset + 4;
521
+                }
522
+                
523
+                theWindow.setWidgetZOrder(zOrder);
524
+             }
525
+
526
+             // the number of windows with dead widgets
527
+             windowNo = me.readInt16BinaryMessage(message, offset);
528
+             offset = offset + 2;
529
+             
530
+             for (var j = 0; j < windowNo; j++)
531
+             {
532
+                // the window ID
533
+                var windowID = me.readInt32BinaryMessage(message, offset);
534
+                offset = offset + 4;
535
+                
536
+                var theWindow = p2j.screen.getWindow(windowID);
537
+                
538
+                if (theWindow == undefined)
539
+                {
540
+                   console.log("undefined window" + windowID);
541
+                   continue;
542
+                }
543
+                
544
+                // the number of dead widgets in this window
545
+                var widgetNo = me.readInt16BinaryMessage(message, offset);
546
+                offset = offset + 2;
547
+                
548
+                for (var k = 0; k < widgetNo; k++)
549
+                {
550
+                   // the widget ID
551
+                   var wid = me.readInt32BinaryMessage(message, offset);
552
+                   offset = offset + 4;
553
+
554
+                   theWindow.deregisterMouseWidget(wid);
555
+                }
556
+             }
557
+
558
+             // the number of windows with new "any widgets"
559
+             windowNo = me.readInt16BinaryMessage(message, offset);
560
+             offset = offset + 2;
561
+             
562
+             for (var j = 0; j < windowNo; j++)
563
+             {
564
+                // the window ID
565
+                var windowID = me.readInt32BinaryMessage(message, offset);
566
+                offset = offset + 4;
567
+                
568
+                if (!p2j.screen.isExistingWindowId(windowID))
569
+                {
570
+                   console.log("undefined window " + windowID);
571
+                   continue;
572
+                }
573
+                
574
+                var theWindow = p2j.screen.getWindow(windowID);
575
+                
576
+                // the number of new "any widgets" in this window
577
+                var widgetNo = me.readInt16BinaryMessage(message, offset);
578
+                offset = offset + 2;
579
+                
580
+                for (var k = 0; k < widgetNo; k++)
581
+                {
582
+                   // the widget ID
583
+                   var wid = me.readInt32BinaryMessage(message, offset);
584
+                   offset = offset + 4;
585
+                   theWindow.deregisterAnyMouseWidget(wid);
586
+                   theWindow.registerAnyMouseWidget(wid);
587
+                }
588
+             }
589
+
590
+             // the number of windows with dead "any widgets"
591
+             windowNo = me.readInt16BinaryMessage(message, offset);
592
+             offset = offset + 2;
593
+             
594
+             for (var j = 0; j < windowNo; j++)
595
+             {
596
+                // the window ID
597
+                var windowID = me.readInt32BinaryMessage(message, offset);
598
+                offset = offset + 4;
599
+                
600
+                var theWindow = p2j.screen.getWindow(windowID);
601
+                
602
+                if (theWindow == undefined)
603
+                {
604
+                   console.log("undefined window" + windowID);
605
+                   continue;
606
+                }
607
+                
608
+                // the number of dead "any widgets" in this window
609
+                var widgetNo = me.readInt16BinaryMessage(message, offset);
610
+                offset = offset + 2;
611
+                
612
+                for (var k = 0; k < widgetNo; k++)
613
+                {
614
+                   // the widget ID
615
+                   var wid = me.readInt32BinaryMessage(message, offset);
616
+                   offset = offset + 4;
617
+
618
+                   theWindow.deregisterAnyMouseWidget(wid);
619
+                }
620
+             }
621
+
622
+             break;
623
+         case 0x97:
624
+            // enable/disable mouse events
625
+            p2j.screen.captureMouseEvents(message[1] == 1);
626
+            break;
627
+         case 0x98:
628
+            // enable/disable OS events
629
+            var wid = me.readInt32BinaryMessage(message, 1);
630
+            p2j.screen.enableOsEvents(wid, message[5] == 1);
631
+            break;
632
+         case 0x99:
633
+            // set window iconification state
634
+            var offset = 1;
635
+
636
+            var windowID = me.readInt32BinaryMessage(message, offset);
637
+            offset = offset + 4;
638
+            
639
+            var iconified = (message[offset] == 1);
640
+            offset = offset + 1;
641
+            
642
+            var theWindow = p2j.screen.getWindow(windowID);
643
+            //p2j.logger.log("recieved 0x99: iconified = " + iconified + " windowId=" + windowID);
644
+            if (iconified)
645
+            {
646
+               theWindow.iconify();
647
+            }
648
+            else
649
+            {
650
+               theWindow.deiconify();
651
+            }
652
+
653
+            break;
654
+         case 0x9A:
655
+            // resizeable window
656
+            var offset = 1;
657
+            
658
+            var windowID = me.readInt32BinaryMessage(message, offset);
659
+            offset = offset + 4;
660
+            
661
+            var theWindow = p2j.screen.getWindow(windowID);
662
+
663
+            theWindow.resizeable = (message[offset] == 1);
664
+            offset = offset + 1;
665
+            
666
+            if (theWindow.resizeable)
667
+            {
668
+               theWindow.minWidth = me.readInt16BinaryMessage(message, offset);
669
+               offset = offset + 2;
670
+               theWindow.minHeight = me.readInt16BinaryMessage(message, offset);
671
+               offset = offset + 2;
672
+               
673
+               theWindow.maxWidth = me.readInt16BinaryMessage(message, offset);
674
+               offset = offset + 2;
675
+               theWindow.maxHeight = me.readInt16BinaryMessage(message, offset);
676
+               offset = offset + 2;
677
+            }
678
+            else
679
+            {
680
+               var rect = theWindow.canvas.getBoundingClientRect();
681
+
682
+               var rwidth  = rect.right - rect.left + 1;
683
+               var rheight = rect.bottom - rect.top + 1;
684
+               
685
+               theWindow.minWidth = rwidth;
686
+               theWindow.minHeight = rheight;
687
+
688
+               theWindow.maxWidth = rwidth;
689
+               theWindow.maxHeight = rheight;
690
+            }
691
+            
692
+            break;
693
+         case 0x9B:
694
+            // current editors selection is changed
695
+            var text = me.readStringBinaryMessage(message, 1);
696
+            p2j.clipboard.setSelection(text);
697
+            break;
698
+         case 0x9C:
699
+            var id = me.readInt32BinaryMessage(message, 1);
700
+            p2j.screen.moveToTop(id);
701
+            break;
702
+         case 0x9D:
703
+            var id = me.readInt32BinaryMessage(message, 1);
704
+            p2j.screen.moveToBottom(id);
705
+            break;
706
+         case 0x9E:
707
+            // change sensitivity for top-level or child window
708
+            var id = me.readInt32BinaryMessage(message, 1);
709
+            var enabled  = message[5] === 0 ? false : true;
710
+            p2j.screen.setWindowEnabled(id, enabled);
711
+            break;
712
+         case 0x9F:
713
+            // font is installed
714
+            var offset = 1;
715
+            
716
+            var msgId = me.readInt32BinaryMessage(message, offset);
717
+            offset = offset + 4;
718
+            
719
+            var fontNameLength = me.readInt32BinaryMessage(message, offset);
720
+            offset = offset + 4;
721
+
722
+            var fontName  = me.readStringBinaryMessageByLength(message, offset, fontNameLength);
723
+            
724
+            var result = p2j.fonts.isFontInstalled(fontName);
725
+            
726
+            me.sendInt8BinaryMessage(0x9F, msgId, result ? 1 : 0);
727
+            break;
728
+         case 0xA0:
729
+            // remove hashes
730
+            var offset = 1;
731
+            
732
+            var msgId = me.readInt32BinaryMessage(message, offset);
733
+            offset = offset + 4;
734
+            
735
+            // window ID
736
+            var windowID = me.readInt32BinaryMessage(message, offset);
737
+            offset = offset + 4;
738
+
739
+            // number of hashes
740
+            var hashNo = me.readInt32BinaryMessage(message, offset);
741
+            offset = offset + 4;
742
+            
743
+            if (windowID == 0)
744
+            {
745
+               for (var i = 0; i < hashNo; i++)
746
+               {
747
+                  // read each hash and remove it
748
+                  var hashBytes = message.slice(offset, offset + 16);
749
+                  var hash = me.createHexString(hashBytes);
750
+                  delete p2j.screen.globalCache[hash];
751
+                  
752
+                  offset += 16;
753
+               }
754
+            }
755
+            else
756
+            {
757
+               var theWindow = p2j.screen.getWindow(windowID);
758
+               
759
+               for (var i = 0; i < hashNo; i++)
760
+               {
761
+                  // read each hash and remove it
762
+                  var hashBytes = message.slice(offset, offset + 16);
763
+                  var hash = me.createHexString(hashBytes);
764
+                  delete theWindow.removeCachedDraw(hash);
765
+                  
766
+                  offset += 16;
767
+               }
768
+            }
769
+            
770
+            me.sendInt8BinaryMessage(0x12, msgId, 1);
771
+            break;
772
+         case 0xfc:
773
+            // ping/pong server answer
774
+            clearTimeout(pingPongWatcher);
775
+            break;
776
+      };
777
+      var t2 = (new Date()).getTime();
778
+      console.log(callNo + ": " + message[0].toString(16) + " done in " + (t2 - t1));
779
+   };
780
+
781
+   /**
782
+    * It is responsible for executing periodically with the given period the provided callback
783
+    * function if the elapsed time exceeds its period. The elapsed time is calulated using
784
+    * the current time and the last checkpoint time.
785
+    * 
786
+    * @param    {Number} maxLifeTime
787
+    *           The maximal life time of the internal timer measured in milliseconds.
788
+    * @param    {Number} period
789
+    *           The period of the internal timer.
790
+    * @param    {Function} callback
791
+    *           The callback function or closure.
792
+    */
793
+   function ControlTimer(maxLifeTime, period, callback)
794
+   {
795
+      /** The time of the last check */
796
+      var checkpoint;
797
+      
798
+      /**
799
+       * If the elapsed time exceeds its period, then it invokes 
800
+       * the callback function. If the elapsed time exceeds the maximal life time,
801
+       * then the timer is stopped.
802
+       */
803
+      function check()
804
+      {
805
+         var test = (new Date()).getTime();
806
+         var elapsed = test - checkpoint;
807
+         if (elapsed > maxLifeTime)
808
+         {
809
+            kill();
810
+         }
811
+         if (elapsed > period)
812
+         {
813
+            callback();
814
+         }
815
+      }
816
+      
817
+      /** The timer id */
818
+      var timer;
819
+      
820
+      /** Indicates the timer is running */
821
+      var running = false;
822
+      
823
+      /**
824
+       * Starts the timer that checks periodicaly with the given rate if the elapsed time
825
+       * exceeds its period. Sets the last checkpoint to the current time.
826
+       */
827
+      function start()
828
+      {
829
+         reset((new Date()).getTime());
830
+         if (running)
831
+         {
832
+            return;
833
+         }
834
+         timer = setInterval(check, period);
835
+         running = true;
836
+      }
837
+      
838
+      this.start = start;
839
+      
840
+      /**
841
+       * Resets the last check time.
842
+       * 
843
+       * @param    {Number} lastActivity
844
+       *           The last activity time in milliseconds.
845
+       */
846
+      function reset(lastActivity)
847
+      {
848
+         checkpoint = lastActivity;
849
+      }
850
+      
851
+      this.reset = reset;
852
+      
853
+      /**
854
+       * Stops and disposes the internal timer. It can be started again.
855
+       */
856
+      function kill()
857
+      {
858
+         if (timer)
859
+         {
860
+            clearInterval(timer);
861
+         }
862
+         running = false;
863
+      }
864
+      
865
+      this.kill = kill;
866
+   }
867
+   
868
+   /**
869
+    * Tries to open a new websocket connection to the server.
870
+    * 
871
+    * @param    {String} url
872
+    *           URL connection string
873
+    */
874
+   function createWebSocket(url)
875
+   {
876
+      // check WebSocket support
877
+      if (!('WebSocket' in window))
878
+      {
879
+         p2j.screen.error('WebSockets are NOT supported by your Browser.');
880
+         return;
881
+      }
882
+      
883
+      ws = new WebSocket(url);
884
+      
885
+      // on web socket open
886
+      ws.onopen = function()
887
+      {
888
+         connected = true;
889
+         ws.binaryType = 'arraybuffer';
890
+         
891
+         connectivityTimer.kill();
892
+         idleTimer.start();
893
+         
894
+         // notify the web socket has opened and the page is loaded
895
+         me.sendNotification(0xff);
896
+      };
897
+
898
+      // on web socket message
899
+      ws.onmessage = function(evt)
900
+      {
901
+         var data = evt.data;
902
+         
903
+         if (data instanceof ArrayBuffer)
904
+         {
905
+            // binary messages
906
+            var message = new Uint8Array(data);
907
+            var isDrawing = message[0] === 0x81;
908
+            if (isDrawing)
909
+            {
910
+               p2j.displayWaitCursor();
911
+            }
912
+            setTimeout(function() { messageHandler(message); }, 0);
913
+            if (isDrawing)
914
+            {
915
+               setTimeout(function() { p2j.restoreCursor(); }, 0);
916
+            }
917
+         }
918
+         else
919
+         {
920
+            // text messages
921
+            var pay = JSON.parse(data);
922
+            setTimeout(
923
+                  function()
924
+                  {
925
+                     switch (pay.c)
926
+                     {
927
+                        case 0:
928
+                           // color palette
929
+                           p2j.screen.palette = pay.p;
930
+                           break;
931
+                     };
932
+                  }, 0);
933
+         }
934
+      }
935
+
936
+      /**
937
+       * Web socket close events handler
938
+       * 
939
+       * @param    {CloseEvent} evt
940
+       *           The close websocket event
941
+       */
942
+      ws.onclose = function(evt)
943
+      { 
944
+         connected = false;
945
+         p2j.screen.error("Connection closed with the returned code " + evt.code); 
946
+         idleTimer.kill();
947
+         // check the normal close reason
948
+         if (evt.code === 1000)
949
+         {
950
+            setTimeout(function() { window.location.replace(referrer); }, 1000);
951
+         }
952
+         else
953
+         {
954
+            connectivityTimer.start();
955
+         }
956
+      };
957
+      
958
+      /**
959
+       * Web socket errors events handler
960
+       * 
961
+       * @param    {Event} evt
962
+       *           The websocket error event
963
+       */
964
+      ws.onerror = function(evt)
965
+      { 
966
+         connected = false;
967
+         idleTimer.kill();
968
+         connectivityTimer.start();
969
+      };
970
+   }
971
    
972
    /**
973
     * Initialize module.
974
@@ -617,653 +1405,76 @@
975
     * @param {object} cfg  configuration.
976
     */
977
    me.init = function(cfg)
978
-   {      
979
+   {
980
       referrer = cfg.referrer;
981
       
982
       maxBinaryMessage = cfg.socket.maxBinaryMessage;
983
       
984
-      if ('WebSocket' in window)
985
-      {
986
-         ws = new WebSocket(cfg.socket.url);
987
-
988
-         // on web socket open
989
-         ws.onopen = function()
990
-         {
991
-            connected = true;
992
-            ws.binaryType = 'arraybuffer';
993
-
994
-            // notify the web socket has opened and the page is loaded
995
-            me.sendNotification(0xff);
996
-         };
997
-
998
-         /** Web socket message handler */
999
-         var messageHandler = function(message)
1000
-         {
1001
-            var t1 = (new Date()).getTime();
1002
-            callNo = callNo + 1;
1003
-
1004
-            switch (message[0])
1005
-            {
1006
-               case 0x80:
1007
-                  // clear screen 
1008
-                  p2j.screen.clear();                        
1009
-                  break;
1010
-               case 0x81:
1011
-                  p2j.screen.drawRectangles(message);
1012
-                  var t2 = (new Date()).getTime();
1013
-                  console.log(callNo + ":" + drawNo + " draw: " + message.length + " done in " + (t2 - t1));
1014
-                  return;
1015
-               case 0x82:
1016
-                  // set cursor position
1017
-                  p2j.screen.setCursorPosition(message[1], message[2]);                        
1018
-                  break;
1019
-               case 0x83:
1020
-                  // show cursor
1021
-                  p2j.screen.setCursorStatus(message[1]);                        
1022
-                  break;
1023
-               case 0x84:
1024
-                  // message beep
1025
-                  p2j.sound.beep();                        
1026
-                  break;
1027
-               case 0x85:
1028
-                  // quit
1029
-                  window.location.replace(referrer);                        
1030
-                  break;
1031
-               case 0x86:
1032
-                  // switch mode p2j/vt100
1033
-                  p2j.keyboard.vt100 = (message[1] == 0) ? false : true;          
1034
-                  break;
1035
-               case 0x87:
1036
-                  // server-driven request for clipboard contents
1037
-                  p2j.clipboard.sendClipboardContents();          
1038
-                  break;
1039
-               case 0x88:
1040
-                  // The clipboard is changed.
1041
-                  var text = me.readStringBinaryMessage(message, 1);
1042
-                  p2j.clipboard.writeClipboard(text);
1043
-                  break;
1044
-               case 0x89:
1045
-                  // create a top-level window with the given id
1046
-                  var id = me.readInt32BinaryMessage(message, 1);
1047
-                  p2j.screen.createWindow(id);
1048
-                  break;
1049
-               case 0x8A:
1050
-                  // create a child window with the given id, owner and title
1051
-                  var id    = me.readInt32BinaryMessage(message, 1);
1052
-                  var owner = me.readInt32BinaryMessage(message, 5);
1053
-                  var title = me.readStringBinaryMessage(message, 9);
1054
-                  p2j.screen.createChildWindow(id, owner);
1055
-                  break;
1056
-               case 0x8B:
1057
-                  // destroy top-level or child window
1058
-                  var id = me.readInt32BinaryMessage(message, 1);
1059
-                  var numberImages = me.readInt32BinaryMessage(message, 5);
1060
-                  var images = [];
1061
-                  for (var i = 0; i < numberImages; i++)
1062
-                  {
1063
-                     images[i] = me.readInt32BinaryMessage(message, 9 + (i * 4));
1064
-                  }
1065
-                  p2j.screen.destroyWindow(id, images);
1066
-                  break;
1067
-               case 0x8C:
1068
-                  // change visibility for top-level or child window
1069
-                  var id = me.readInt32BinaryMessage(message, 1);
1070
-                  var visible = message[5] === 0 ? false : true;
1071
-                  p2j.screen.setWindowVisible(id, visible);
1072
-                  break;
1073
-                  
1074
-               // font and metrics related requests
1075
-               case 0x8D:
1076
-                  // paragraph height
1077
-                  
1078
-                  var offset = 1;
1079
-                  
1080
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1081
-                  offset = offset + 4;
1082
-                  
1083
-                  var textLength = me.readInt32BinaryMessage(message, offset);
1084
-                  offset = offset + 4;
1085
-
1086
-                  var text  = me.readStringBinaryMessageByLength(message, offset, textLength);
1087
-                  offset = offset + textLength * 2;
1088
-                  
1089
-                  var font = me.readInt32BinaryMessage(message, offset);
1090
-                  offset = offset + 4;
1091
-
1092
-                  var maxWidth = me.readInt16BinaryMessage(message, offset);
1093
-                  offset = offset + 2;
1094
-
1095
-                  var pheight = p2j.screen.layoutParagraphWorker(null,
1096
-                                                                 text,
1097
-                                                                 font, 
1098
-                                                                 0,
1099
-                                                                 0, 
1100
-                                                                 maxWidth);
1101
-
1102
-                  me.sendInt16BinaryMessage(0x06, msgId, pheight);
1103
-                  break;
1104
-               case 0x8E:
1105
-                  // text height
1106
-                  
1107
-                  var offset = 1;
1108
-
1109
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1110
-                  offset = offset + 4;
1111
-                  
1112
-                  var textLength = me.readInt32BinaryMessage(message, offset);
1113
-                  offset = offset + 4;
1114
-
1115
-                  var text  = me.readStringBinaryMessageByLength(message, offset, textLength);
1116
-                  offset = offset + textLength * 2;
1117
-
1118
-                  var font = me.readInt32BinaryMessage(message, offset);
1119
-                  
1120
-                  var theight = p2j.fonts.getTextHeight(font, text);
1121
-
1122
-                  me.sendInt8BinaryMessage(0x07, msgId, theight);
1123
-                  break;
1124
-               case 0x8F:
1125
-                  // text width
1126
-
1127
-                  var offset = 1;
1128
-                  
1129
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1130
-                  offset = offset + 4;
1131
-                  
1132
-                  var textLength = me.readInt32BinaryMessage(message, offset);
1133
-                  offset = offset + 4;
1134
-
1135
-                  var text  = me.readStringBinaryMessageByLength(message, offset, textLength);
1136
-                  offset = offset + textLength * 2;
1137
-
1138
-                  var font = me.readInt32BinaryMessage(message, offset);
1139
-                  offset = offset + 4;
1140
-                  
1141
-                  var twidth = p2j.fonts.getTextWidth(font, text);
1142
-                  
1143
-                  me.sendInt16BinaryMessage(0x08, msgId, twidth);
1144
-                  break;
1145
-               case 0x90:
1146
-                  // font height
1147
-
1148
-                  var offset = 1;
1149
-
1150
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1151
-                  offset = offset + 4;
1152
-
1153
-                  var font = me.readInt32BinaryMessage(message, offset);
1154
-                  offset = offset + 4;
1155
-                  
1156
-                  var fheight = p2j.fonts.getFontHeight(font);
1157
-                  
1158
-                  me.sendInt8BinaryMessage(0x09, msgId, fheight);
1159
-                  break;
1160
-               case 0x91:
1161
-                  // font widths
1162
-
1163
-                  var offset = 1;
1164
-                  
1165
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1166
-                  offset = offset + 4;
1167
-                  
1168
-                  var font = me.readInt32BinaryMessage(message, offset);
1169
-                  offset = offset + 4;
1170
-                  
1171
-                  var fwidths = p2j.fonts.getFontWidths(font);
1172
-                  
1173
-                  me.sendByteArrayBinaryMessage(0x0A, msgId, fwidths);
1174
-                  break;
1175
-               case 0x92:
1176
-                  // create font
1177
-
1178
-                  var offset = 1;
1179
-                  
1180
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1181
-                  offset = offset + 4;
1182
-                  
1183
-                  var font = me.readInt32BinaryMessage(message, offset);
1184
-                  offset = offset + 4;
1185
-                  
1186
-                  var nameLength = me.readInt16BinaryMessage(message, offset);
1187
-                  offset = offset + 2;
1188
-
1189
-                  var name  = me.readStringBinaryMessageByLength(message, offset, nameLength);
1190
-                  offset = offset + nameLength * 2;
1191
-                  
1192
-                  var size = message[offset];
1193
-                  offset = offset + 1;
1194
-                  
1195
-                  var style = message[offset];
1196
-                  offset = offset + 1;
1197
-                  
1198
-                  var defLength = me.readInt32BinaryMessage(message, offset);
1199
-                  offset = offset + 4;
1200
-                  
1201
-                  var b64font = "";
1202
-                  if (defLength > 0)
1203
-                  {
1204
-                     var binFont = '';
1205
-                     for (var i = 0; i < defLength; i++)
1206
-                     {
1207
-                        binFont += String.fromCharCode(message[offset]);
1208
-                        offset = offset + 1;
1209
-                     }
1210
-                     b64font = window.btoa(binFont);
1211
-                  }
1212
-
1213
-                  var fontId = p2j.fonts.createFont(font, name, size, style, b64font);
1214
-                  
1215
-                  me.sendInt32BinaryMessage(0x0B, msgId, fontId);
1216
-                  break;
1217
-               case 0x93:
1218
-                  // derive font
1219
-
1220
-                  var offset = 1;
1221
-                  
1222
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1223
-                  offset = offset + 4;
1224
-                  
1225
-                  var font = me.readInt32BinaryMessage(message, offset);
1226
-                  offset = offset + 4;
1227
-
1228
-                  p2j.fonts.deriveFont(font);
1229
-                   
1230
-                  me.sendInt8BinaryMessage(0x0C, msgId, 1);
1231
-                  break;
1232
-               case 0x94:
1233
-                  // set cursor style
1234
-                  var styleId = me.readInt32BinaryMessage(message, 1);
1235
-                  var wid = me.readInt32BinaryMessage(message, 5);
1236
-                  p2j.screen.setCursorStyle(styleId, wid);
1237
-                  break;
1238
-               case 0x95:
1239
-                  // restack windows
1240
-                  var num = me.readInt32BinaryMessage(message, 1);
1241
-                  var winids = [];
1242
-                  for (var i = 0; i < num; i++)
1243
-                  {
1244
-                     winids.push(me.readInt32BinaryMessage(message, 5 + (i * 4)));
1245
-                  }
1246
-                  p2j.screen.restackZOrderEntries(winids);
1247
-                  break;
1248
-               case 0x96:
1249
-                   // register/deregister widgets for mouse actions
1250
-                   
1251
-                   var offset = 1;
1252
-                   
1253
-                   // the number of windows with new widgets
1254
-                   var windowNo = me.readInt16BinaryMessage(message, offset);
1255
-                   offset = offset + 2;
1256
-                   
1257
-                   for (var j = 0; j < windowNo; j++)
1258
-                   {
1259
-                      // the window ID
1260
-                      var windowID = me.readInt32BinaryMessage(message, offset);
1261
-                      offset = offset + 4;
1262
-                      
1263
-                      if (!p2j.screen.isExistingWindowId(windowID))
1264
-                      {
1265
-                         console.log("undefined window " + windowID);
1266
-                         continue;
1267
-                      }
1268
-                      
1269
-                      var theWindow = p2j.screen.getWindow(windowID);
1270
-                      
1271
-                      // the number of new widgets in this window
1272
-                      var widgetNo = me.readInt16BinaryMessage(message, offset);
1273
-                      offset = offset + 2;
1274
-                      
1275
-                      for (var k = 0; k < widgetNo; k++)
1276
-                      {
1277
-                         // the widget ID
1278
-                         var wid = me.readInt32BinaryMessage(message, offset);
1279
-                         offset = offset + 4;
1280
-
1281
-                         // the coordinates
1282
-                         var x = me.readInt16BinaryMessage(message, offset);
1283
-                         offset = offset + 2;
1284
-                         
1285
-                         var y = me.readInt16BinaryMessage(message, offset);
1286
-                         offset = offset + 2;
1287
-         
1288
-                         // the size
1289
-                         var width = me.readInt16BinaryMessage(message, offset);
1290
-                         offset = offset + 2;
1291
-         
1292
-                         var height = me.readInt16BinaryMessage(message, offset);
1293
-                         offset = offset + 2;
1294
-         
1295
-                         // bit-encoded mouse ops: each bit from 1 to 11, if set, represents a mouse
1296
-                         // operation as defined in p2j.screen.mouseOps
1297
-                         var actions = me.readInt32BinaryMessage(message, offset);
1298
-                         offset = offset + 4;
1299
-                         theWindow.deregisterMouseWidget(wid);
1300
-                         theWindow.registerMouseWidget(wid, x, y, width, height, actions);
1301
-                      }
1302
-                      
1303
-                      var allWNo = me.readInt16BinaryMessage(message, offset);
1304
-                      offset = offset + 2;
1305
-                      var zOrder = [];
1306
-                      
1307
-                      for (var k = 0; k < allWNo; k++)
1308
-                      {
1309
-                        zOrder[k] = me.readInt32BinaryMessage(message, offset);
1310
-                        offset = offset + 4;
1311
-                      }
1312
-                      
1313
-                      theWindow.setWidgetZOrder(zOrder);
1314
-                   }
1315
-   
1316
-                   // the number of windows with dead widgets
1317
-                   windowNo = me.readInt16BinaryMessage(message, offset);
1318
-                   offset = offset + 2;
1319
-                   
1320
-                   for (var j = 0; j < windowNo; j++)
1321
-                   {
1322
-                      // the window ID
1323
-                      var windowID = me.readInt32BinaryMessage(message, offset);
1324
-                      offset = offset + 4;
1325
-                      
1326
-                      var theWindow = p2j.screen.getWindow(windowID);
1327
-                      
1328
-                      if (theWindow == undefined)
1329
-                      {
1330
-                         console.log("undefined window" + windowID);
1331
-                         continue;
1332
-                      }
1333
-                      
1334
-                      // the number of dead widgets in this window
1335
-                      var widgetNo = me.readInt16BinaryMessage(message, offset);
1336
-                      offset = offset + 2;
1337
-                      
1338
-                      for (var k = 0; k < widgetNo; k++)
1339
-                      {
1340
-                         // the widget ID
1341
-                         var wid = me.readInt32BinaryMessage(message, offset);
1342
-                         offset = offset + 4;
1343
-
1344
-                         theWindow.deregisterMouseWidget(wid);
1345
-                      }
1346
-                   }
1347
-
1348
-                   // the number of windows with new "any widgets"
1349
-                   windowNo = me.readInt16BinaryMessage(message, offset);
1350
-                   offset = offset + 2;
1351
-                   
1352
-                   for (var j = 0; j < windowNo; j++)
1353
-                   {
1354
-                      // the window ID
1355
-                      var windowID = me.readInt32BinaryMessage(message, offset);
1356
-                      offset = offset + 4;
1357
-                      
1358
-                      if (!p2j.screen.isExistingWindowId(windowID))
1359
-                      {
1360
-                         console.log("undefined window " + windowID);
1361
-                         continue;
1362
-                      }
1363
-                      
1364
-                      var theWindow = p2j.screen.getWindow(windowID);
1365
-                      
1366
-                      // the number of new "any widgets" in this window
1367
-                      var widgetNo = me.readInt16BinaryMessage(message, offset);
1368
-                      offset = offset + 2;
1369
-                      
1370
-                      for (var k = 0; k < widgetNo; k++)
1371
-                      {
1372
-                         // the widget ID
1373
-                         var wid = me.readInt32BinaryMessage(message, offset);
1374
-                         offset = offset + 4;
1375
-                         theWindow.deregisterAnyMouseWidget(wid);
1376
-                         theWindow.registerAnyMouseWidget(wid);
1377
-                      }
1378
-                   }
1379
-   
1380
-                   // the number of windows with dead "any widgets"
1381
-                   windowNo = me.readInt16BinaryMessage(message, offset);
1382
-                   offset = offset + 2;
1383
-                   
1384
-                   for (var j = 0; j < windowNo; j++)
1385
-                   {
1386
-                      // the window ID
1387
-                      var windowID = me.readInt32BinaryMessage(message, offset);
1388
-                      offset = offset + 4;
1389
-                      
1390
-                      var theWindow = p2j.screen.getWindow(windowID);
1391
-                      
1392
-                      if (theWindow == undefined)
1393
-                      {
1394
-                         console.log("undefined window" + windowID);
1395
-                         continue;
1396
-                      }
1397
-                      
1398
-                      // the number of dead "any widgets" in this window
1399
-                      var widgetNo = me.readInt16BinaryMessage(message, offset);
1400
-                      offset = offset + 2;
1401
-                      
1402
-                      for (var k = 0; k < widgetNo; k++)
1403
-                      {
1404
-                         // the widget ID
1405
-                         var wid = me.readInt32BinaryMessage(message, offset);
1406
-                         offset = offset + 4;
1407
-
1408
-                         theWindow.deregisterAnyMouseWidget(wid);
1409
-                      }
1410
-                   }
1411
-
1412
-                   break;
1413
-               case 0x97:
1414
-                  // enable/disable mouse events
1415
-                  p2j.screen.captureMouseEvents(message[1] == 1);
1416
-                  break;
1417
-               case 0x98:
1418
-                  // enable/disable OS events
1419
-                  var wid = me.readInt32BinaryMessage(message, 1);
1420
-                  p2j.screen.enableOsEvents(wid, message[5] == 1);
1421
-                  break;
1422
-               case 0x99:
1423
-                  // set window iconification state
1424
-                  var offset = 1;
1425
-
1426
-                  var windowID = me.readInt32BinaryMessage(message, offset);
1427
-                  offset = offset + 4;
1428
-                  
1429
-                  var iconified = (message[offset] == 1);
1430
-                  offset = offset + 1;
1431
-                  
1432
-                  var theWindow = p2j.screen.getWindow(windowID);
1433
-                  //p2j.logger.log("recieved 0x99: iconified = " + iconified + " windowId=" + windowID);
1434
-                  if (iconified)
1435
-                  {
1436
-                     theWindow.iconify();
1437
-                  }
1438
-                  else
1439
-                  {
1440
-                     theWindow.deiconify();
1441
-                  }
1442
-
1443
-                  break;
1444
-               case 0x9A:
1445
-                  // resizeable window
1446
-                  var offset = 1;
1447
-                  
1448
-                  var windowID = me.readInt32BinaryMessage(message, offset);
1449
-                  offset = offset + 4;
1450
-                  
1451
-                  var theWindow = p2j.screen.getWindow(windowID);
1452
-
1453
-                  theWindow.resizeable = (message[offset] == 1);
1454
-                  offset = offset + 1;
1455
-                  
1456
-                  if (theWindow.resizeable)
1457
-                  {
1458
-                     theWindow.minWidth = me.readInt16BinaryMessage(message, offset);
1459
-                     offset = offset + 2;
1460
-                     theWindow.minHeight = me.readInt16BinaryMessage(message, offset);
1461
-                     offset = offset + 2;
1462
-                     
1463
-                     theWindow.maxWidth = me.readInt16BinaryMessage(message, offset);
1464
-                     offset = offset + 2;
1465
-                     theWindow.maxHeight = me.readInt16BinaryMessage(message, offset);
1466
-                     offset = offset + 2;
1467
-                  }
1468
-                  else
1469
-                  {
1470
-                     var rect = theWindow.canvas.getBoundingClientRect();
1471
-
1472
-                     var rwidth  = rect.right - rect.left + 1;
1473
-                     var rheight = rect.bottom - rect.top + 1;
1474
-                     
1475
-                     theWindow.minWidth = rwidth;
1476
-                     theWindow.minHeight = rheight;
1477
-
1478
-                     theWindow.maxWidth = rwidth;
1479
-                     theWindow.maxHeight = rheight;
1480
-                  }
1481
-                  
1482
-                  break;
1483
-               case 0x9B:
1484
-                  // current editors selection is changed
1485
-                  var text = me.readStringBinaryMessage(message, 1);
1486
-                  p2j.clipboard.setSelection(text);
1487
-                  break;
1488
-               case 0x9C:
1489
-                  var id = me.readInt32BinaryMessage(message, 1);
1490
-                  p2j.screen.moveToTop(id);
1491
-                  break;
1492
-               case 0x9D:
1493
-                  var id = me.readInt32BinaryMessage(message, 1);
1494
-                  p2j.screen.moveToBottom(id);
1495
-                  break;
1496
-               case 0x9E:
1497
-                  // change sensitivity for top-level or child window
1498
-                  var id = me.readInt32BinaryMessage(message, 1);
1499
-                  var enabled  = message[5] === 0 ? false : true;
1500
-                  p2j.screen.setWindowEnabled(id, enabled);
1501
-                  break;
1502
-               case 0x9F:
1503
-                  // font is installed
1504
-                  var offset = 1;
1505
-                  
1506
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1507
-                  offset = offset + 4;
1508
-                  
1509
-                  var fontNameLength = me.readInt32BinaryMessage(message, offset);
1510
-                  offset = offset + 4;
1511
-
1512
-                  var fontName  = me.readStringBinaryMessageByLength(message, offset, fontNameLength);
1513
-                  
1514
-                  var result = p2j.fonts.isFontInstalled(fontName);
1515
-                  
1516
-                  me.sendInt8BinaryMessage(0x9F, msgId, result ? 1 : 0);
1517
-                  break;
1518
-               case 0xA0:
1519
-                  // remove hashes
1520
-                  var offset = 1;
1521
-                  
1522
-                  var msgId = me.readInt32BinaryMessage(message, offset);
1523
-                  offset = offset + 4;
1524
-                  
1525
-                  // window ID
1526
-                  var windowID = me.readInt32BinaryMessage(message, offset);
1527
-                  offset = offset + 4;
1528
-
1529
-                  // number of hashes
1530
-                  var hashNo = me.readInt32BinaryMessage(message, offset);
1531
-                  offset = offset + 4;
1532
-                  
1533
-                  if (windowID == 0)
1534
-                  {
1535
-                     for (var i = 0; i < hashNo; i++)
1536
-                     {
1537
-                        // read each hash and remove it
1538
-                        var hashBytes = message.slice(offset, offset + 16);
1539
-                        var hash = me.createHexString(hashBytes);
1540
-                        delete p2j.screen.globalCache[hash];
1541
-                        
1542
-                        offset += 16;
1543
-                     }
1544
-                  }
1545
-                  else
1546
-                  {
1547
-                     var theWindow = p2j.screen.getWindow(windowID);
1548
-                     
1549
-                     for (var i = 0; i < hashNo; i++)
1550
-                     {
1551
-                        // read each hash and remove it
1552
-                        var hashBytes = message.slice(offset, offset + 16);
1553
-                        var hash = me.createHexString(hashBytes);
1554
-                        delete theWindow.removeCachedDraw(hash);
1555
-                        
1556
-                        offset += 16;
1557
-                     }
1558
-                  }
1559
-                  
1560
-                  me.sendInt8BinaryMessage(0x12, msgId, 1);
1561
-                  break;
1562
-            };
1563
-            var t2 = (new Date()).getTime();
1564
-            console.log(callNo + ": " + message[0].toString(16) + " done in " + (t2 - t1));
1565
-         };
1566
-         // on web socket message
1567
-         ws.onmessage = function(evt)
1568
-         {
1569
-            var data = evt.data;
1570
-            
1571
-            if (data instanceof ArrayBuffer)
1572
-            {
1573
-               // binary messages
1574
-               var message = new Uint8Array(data);
1575
-               var isDrawing = message[0] === 0x81;
1576
-               if (isDrawing)
1577
-               {
1578
-                  p2j.displayWaitCursor();
1579
-               }
1580
-               executor.executeTask({ execute : function() { messageHandler(message); } }, false);
1581
-               if (isDrawing)
1582
-               {
1583
-                  executor.executeTask({ execute : function() { p2j.restoreCursor(); } }, false);
1584
-               }
1585
-            }
1586
-            else
1587
-            {
1588
-               // text messages
1589
-               var pay = JSON.parse(data);
1590
-               executor.executeTask(
1591
-                     { execute : function()
1592
-                                 {
1593
-                                    switch (pay.c)
1594
-                                    {
1595
-                                       case 0:
1596
-                                          // color palette
1597
-                                          p2j.screen.palette = pay.p;
1598
-                                          break;
1599
-                                    };
1600
-                                 }
1601
-                     }, false);
1602
-            }
1603
-         }
1604
-
1605
-         // on web socket close
1606
-         ws.onclose = function()
1607
-         { 
1608
-            connected = false;
1609
-            p2j.screen.error('Connection closed by remote host.'); 
1610
-            window.setInterval(function() { window.location.replace(referrer); }, 5000);
1611
-         };
1612
-         
1613
-         // refresh
1614
-         window.onpagehide = function() 
1615
-         {
1616
-            if (connected)
1617
-            {
1618
-               ws.close();
1619
-            }
1620
-         };                  
1621
-      } 
1622
-      else 
1623
-      {
1624
-         p2j.screen.error('WebSockets are NOT supported by your Browser.');
1625
-      }      
1626
+      maxIdleTime      = cfg.socket.maxIdleTime;
1627
+      
1628
+      watchdogTimeout  = cfg.socket.watchdogTimeout;
1629
+      
1630
+      socketUrl        = cfg.socket.url;
1631
+      
1632
+      /**
1633
+       * Sends ping and pong notifications to the server and starts the ping/pong watcher timer.
1634
+       */
1635
+      function ping()
1636
+      {
1637
+         //send Ping/Pong message
1638
+         me.sendNotification(0xFC);
1639
+         // start ping/pong watcher
1640
+         pingPongWatcher = setTimeout(
1641
+               function()
1642
+               {
1643
+                  // 5000 ms has been elapsed, but the server hasn't replied yet on the ping sent
1644
+                  // by the client. If ws is in OPEN state, then send new ping/pong
1645
+                  if (ws && ws.readyState == 1)
1646
+                  {
1647
+                     ping();
1648
+                  }
1649
+               }, 5000);
1650
+         console.debug("Ping/Pong " + (new Date()).getTime());
1651
+      };
1652
+      
1653
+      idleTimer = new ControlTimer(maxIdleTime, maxIdleTime / 3, ping);
1654
+      
1655
+      /**
1656
+       * Connect to the websocket server via the given connection url.
1657
+       */
1658
+      function connect()
1659
+      {
1660
+         // don't reconnect if the websocket state is CONNECTING(0) or OPEN(1) or CLOSING(2)
1661
+         if (ws && ws.readyState !== 3)
1662
+         {
1663
+            console.debug("Connection in process " + (new Date()).getTime());
1664
+            return;
1665
+         }
1666
+         createWebSocket(socketUrl);
1667
+         console.debug("Try to connect the server " + (new Date()).getTime());
1668
+      };
1669
+      
1670
+      connectivityTimer = new ControlTimer(Math.max(watchdogTimeout, maxIdleTime), 5000, connect);
1671
+      
1672
+      // TODO: offline mode
1673
+      window.addEventListener(
1674
+            "offline",
1675
+            function(e)
1676
+            {
1677
+               console.debug("offline");
1678
+               connectivityTimer.start();
1679
+             }, false);
1680
+      
1681
+      // TODO: refresh
1682
+      window.onpagehide = function() 
1683
+      {
1684
+         if (connected)
1685
+         {
1686
+            ws.close();
1687
+         }
1688
+      };
1689
+      
1690
+      createWebSocket(socketUrl);
1691
    };
1692
 
1693
    /**
1694