From a5df24b488a6ef99f9e06313265a95985a9ff691 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 22 Sep 2011 10:36:23 -0500 Subject: [PATCH] Viewport clip/drag for mobile/touchscreen devices. API changes (forward compatible): - Display: add 'viewport' conf option to turn on and off viewport mode. - RFB: add 'viewportDrag' option to enable/disable viewport dragging mode. Other: - Add clip mode setting to default UI. For touch devices, clipping is forced on. - Use CSS media queries to adjust visual elements based on screen size. Especially disconnected logo size/position and button text size. - Catch page unload while connected and give a confirm dialog. - Change mouse button selector to a single button that changes between ' ', 'L', 'M', 'R' when clicked (empty means mouse is just being moved and doesn't send clicks). - include/ui.js:setViewClip() routine sets the clipping of the viewport to the current size of the viewport area (if clipping is enabled). - include/ui.js:setViewDrag() toggles/enables/disables viewport dragging mode. - Add several images for the UI and for Apple devices: - images/clipboard.png: clipboard menu icon - images/connect.png: connect menu icon - images/disconnect.png: disconnect button icon - images/keyboard.png: show keyboard button - images/move.png: viewport drag/move toggle button - images/settings.png: settings menu icon - images/screen_320x460.png: iOS app/desktop link start image - images/screen_57x57.png: iOS app icon - images/screen_700x700.png: full size noVNC image --- images/clipboard.png | Bin 0 -> 1509 bytes images/connect.png | Bin 0 -> 1513 bytes images/disconnect.png | Bin 0 -> 2105 bytes images/keyboard.png | Bin 0 -> 1838 bytes images/move.png | Bin 0 -> 1493 bytes images/screen_320x460.png | Bin 0 -> 12778 bytes images/screen_57x57.png | Bin 0 -> 1807 bytes images/screen_700x700.png | Bin 0 -> 17930 bytes images/settings.png | Bin 0 -> 2326 bytes include/base.css | 123 +++++++++--- include/black.css | 5 + include/blue.css | 5 + include/display.js | 35 +++- include/rfb.js | 44 ++++- include/ui.js | 228 +++++++++++++++-------- include/mobile.css => tests/viewport.css | 0 tests/viewport.html | 20 +- vnc.html | 104 ++++++----- 18 files changed, 402 insertions(+), 162 deletions(-) create mode 100644 images/clipboard.png create mode 100644 images/connect.png create mode 100644 images/disconnect.png create mode 100644 images/keyboard.png create mode 100644 images/move.png create mode 100644 images/screen_320x460.png create mode 100644 images/screen_57x57.png create mode 100644 images/screen_700x700.png create mode 100644 images/settings.png rename include/mobile.css => tests/viewport.css (100%) diff --git a/images/clipboard.png b/images/clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..571a9098d6d754d43763ec93dc9b88ca7988b1f3 GIT binary patch literal 1509 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 P00000NkvXXu0mjfM}U$e literal 0 HcmV?d00001 diff --git a/images/disconnect.png b/images/disconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..d2dcf9c092c733b26e80ce847d68460c32acfc1c GIT binary patch literal 2105 zcmV-92*&q`P)`|vV1z}0000MbVXQnL3MO!Z*l-ZZe((0 zVRIl(X>4DZyGB7YTEio`HF*cN)R_y=) z07-O3Sad^gaCvfRXJ~W)P<3K#X=5NnZ*5^|ZXjrMbZ|N^FJp3LVRUJBWn*t`ZEtRK zE^l&YFKlUJWo~n2b1!gpVr*$+WN&R@X>KoB!~V(u00#w0L_t(og|(M^Y*fb;#(y(+ zz3biUM{o_XaWS^BYx9VxYH%=Ac}WQrQj-ux6-A^H3RG3q1S+LP6;fmp$V0_Ifs`0% zM508B6h(+Cr)Reit7{q?k4!5qmD%sTZ?(=CfKtTcasKti7ki$!?d^Ap?Yoo1E+N)5 ztyuA=c?%a7CwhCc(LF$Kt`umEzo>}v^75^F4I}xyZND~Ed@+>*Z9+V?WaY{u_4DT! z_n$sJ2AWO6F`YF=Yl`N~p=WR~-to~#+qT&D?*EuTn-EVoKmPdPy18?U2EYD#0{D2q z+mW~tiNrfjocPVJwLUNzj-2den-D)~ZfQAEH+OE);F&Xyr_7iTAOyns%{K3y^9*-( zGP9&4wCu6RUTYI#<6RNhCB)igEiH#?>*@+~z}?`tBax$PX0Cyut}bTHm|-?Q@<`im zA$~R`fjvU3`{C-|9c^RIw^rYLabl<z#Kf zv2F5)hbI)Jux-k3-ejb;m7DLqM~P`-#^YWpG|=DGMRi3*;rE-G4z~%>;t}wh<}d5- zyU&cCJ?jN@E4r+^o9HXAkZNp1m?r&iy~THgAP|drKEk#s9~>mMV+ZNxW<)SZ-{HfQ z78esprN(v%5V>-Nx%Kr${$TKTV=0glmF1Ydt&#-=gifABN{L|@2-9Tn=ut{TAp(g+ zM)uKB5-+`kwPcCs7YK(TnIvr(o&xT|R4V1wP=hV$NjQXZTd^aPaK;D$TGMZu zlz#RZh57l!H*G=)fiO*CZ@htwM6fF=Fr2`~wQGc4ewprMl5`fZ29!H@+c(+UFk5=1 za@A0Zh|h=B(Lur6Z)a4H5*Z3XWu-UI>*>MVxs&c_l(Y-1HOeuUNOt%2O-8_LBw1s$ zrcWuRfAR_W)2EYu;)&c{Ff>H|>#uVq9QMGiL;@=oLnV_aXHi-ytY8qGO6ds%v{ISw z<`ROOh9iJLQ4y-40V(B}FQ5ww&}C(C;R3O%SFz)9lx^qq0@tjP5|m}>u@qz-r7lbD z%ux!ILI4%j)#xo-V8H?m!|+T>i3|itZ`;P~<;w|$LbnCiZqbjg0iBg^kBnOjKt+8$ z`nl)O^X7TgZiK@a9UVC&tgTzA+qjXUii+D3$Zla{DR6-&k^Q?17owhh7F}0|;eg9X z1bgpZhA&@c+UO{(T7^;y;rC;0-pu!cK`tFUNc5X;CK4E5gc`uMGqMXYwhe)b=4PyC zo`Kq0q;mqw{(kIz`#9I$PCAjG+At{f`{B_?(OM(&@GZW^I=whBZJJCD44_>Fgdmkl5$Ns3s;$j{4-8^qi>hmEU zd@z&SZ@)!b7AeQaGa)bxV_XW->Gj9oeYf(*Yu7H;TFcbH00@Cfr@8*|$CM?Ln0a}K z`SY=l9_9S5T^_h=o$tSo%FAPVULJbE0{GwqzS^;aDVbGO#f5=^{_be> zsTZ{V)GPAwmr{qsUsg3Y-ygel$@pJLC_qR_Wpy=Y`uh61hlhW(RqL;^@h02}f2DPD zz_K3x^QlvxhwAF=P-*FZ1?+*>)NuONt((_|haVmfKB)qkbP01Nlx#MP4_g*3`a!&_ zOU5FRy8?E{!Z4_;sp0Rvz2RfA*qY;T0T|X=t8Ay`F_9D zzh9qXLtV@d5dcgaIJWF@v!bYQP38APuB|1dz~L jr>2a5H-FFnpU=MmIa(=#x*?@{00000NkvXXu0mjfQa$FT literal 0 HcmV?d00001 diff --git a/images/keyboard.png b/images/keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c47ce4172c17abebe8af44f000bf44da973e25 GIT binary patch literal 1838 zcmV+}2hsS6P)Mh5=KJ?A0000YbVXQnQ*U*0V`TtnbaZe!FE46oZEay= zE^T#lX=7+%Y-}!LdN1{9$ix5u07-O3Sad^gaCvfRXJ~W)P<3K#X=5NnZ*5^|ZXjrM zbZ|N^FJp3LVRUJBWn*t`ZEtRKE^l&YFKlUJWo~n2b1!gpVr*$+WN&R@X>KoB!~V(u z00rhrL_t(oh3%J3Y!llR$A8yjkL}4gw&TS4GI>cHBZ%U*5g%=o!lKfvAVC!?Dpl%& zyz=N0sXVa^k-BJvkg9ZxR1}3EB`m74N!0}qAgC$`co70A!J?_)g{N2{i5+`9Gxv5w z+z|4?Q;X=LM>?82bI!fzum7BL?!7`O#ita{CyoAp0RIaBydUZ~G&Gd5ZTtH|h!|ii zrT(dD+VeAK&Sd@*0Ai;q7#JAvnx^@sQp%&0`pfF->OTO-FpRhmqD2T%3p~r^a9#)9f2S(&oI7`J>AL{IKaP*}mXR|y27EG>OQ zFc?MCe4@9vw|Zt~rXTp_mH_;I|54L4nVXx#wrygu7_VQyCKwC?kjv!=hr=u{FC(SI zG)>;Td4tdA!|(SarDSR85^ZhYFhBnlRaJjL$_7?fzd%Y!MMXu&#$WJG5;itAwkV~@ z=ks{IUZ$s~sjRHz<;$1M&(9N$M#*F{=()YjI({T7&1TC0T-S?bH6W#=RH~*_GH_j&d_GTIU7d0I^y#Ke0eHRM zA19N^O3SinYir}kks}m~MfUC6$AJR}Fin%5o*rVc7`u1xrnk2jr4$Db9^}xWLu9kr zw>eOtzP_G{iV6ya0?A|&+qTK)^TgxvO2=`2uxSoNB9Wg(A`x$?RKl_>Mn*;7AV4yi zq);gE>eVaS+uM2e>=}N)pRTShN~ID~O6uzB%GIN38k(jZc#nY-Cr;=`j~=Zq7K=OEE0)CYS#l0N-6sK`hM8g*Z0>%A`x0xSU}fxT-U|2tSxsj z$8peg9ox21N}*{QQc7&wMoP)->@2#jQ&Ure&*#H&9D>0hPoF-0egFRb-6JC-%Vh>a zq0mojYHC6+Uc8{CrG=T98H5m|QmKtqQz;D_1a06HU{&b?X+5jg4Hse3?)vM00a9p-{*O27`yn0E7_6 z;NakAj^j{ORYgxv&!#PQs}>xK#maV{&qrrxXW5oWB#=@PjYiANz3rHv3n6~5lqz_; zySv*HiA2b$Ab_8kH=G9bnRU! zh3mTI9d9kbTHv(}Y`ty$?^=7VuCC%ZjsOJU5y@mSe)#a=ul#<0hm_L$*3q-wX?de< zZ42H`gb)%?Qp#_iKY#wm+qZB3T`9FJlu`&Gbif4sfX2tUut>lGia=f|Wy1&D3qH1d c@~hdu0Pfbi(n+VML;wH)07*qoM6N<$g1+iy%K!iX literal 0 HcmV?d00001 diff --git a/images/move.png b/images/move.png new file mode 100644 index 0000000000000000000000000000000000000000..b6156b58d95d722198c0cd12376c385c10b0c73d GIT binary patch literal 1493 zcmV;`1uFW9P)o%Ze?=j`}Z#Z001F$MObuGZ*_8GWdLY&bZ|N^FKTIRZDC_B zZFO^LV`yP)Y%XJZX=dYI0000b7gZcaCKsAX=7w>ZDDC{FImI>$^ZZa zk4Z#9R9J<@mtAa=RUF2D=Y89*Yy0lQb*?M0M9qW{=5#YC-k6x+7ZPv;G)5CXBEbtH zR}!v>;)MwsjB+LM#uyd(R3MrVlthg)89JO}gD{v#=~%lJ_O{)}+rGz(cBNh4x9tVf zr#VT}|2gM*&U60%b54bnlK&Hq*)MxP+}q);_Uu83`gv!S=J}b)v0XcM?7o!~5NvKf z+_iT7eNu>d=TF4rD-Ip{0^p6DfJF;he3OR3Tl>B%Ie5dfZ7hA@UfcNB-}iB0Xw){o zthI@)U5|rM+97WXW^tx_$ae6i7duOm1FU*-F=x7mN{YybQc*8pb5|!@x;hb1viYiA z?Ay7jq)?GZTv1rE#=;B+Vi{2BqjkAPoe=$~<#8#P*68Z0MEnVFU_G>v2nguz4zt)3cq*0Vi-YAOT z{)P3t@_aj~l*#t@P85wPD#L9Xma}n92LKD3YI*I2hxzl;2>v<`fkutSdaorpfXk^8 z^lOOh$6x2+v9?7x911R{%E!A_bK>lvjR28xVAI;p!d2CKS<&7?a>}SEsa!4x3z}-E zc02Lcc~F!)c6+N`y!_llr5&)U1drQ^$L%ajJ`1O+&~%TzQ~4Z4IWYe>K_LnSWHWcZ zk{9mQ5nO3Bo8s_ZeG-=?Mv+gLt|1Poq{6m(9fP5Lg4 zaDC(!@x*jRjwiSg(YX=TIoorU-}i_5x-R zPtLG$_YrD+nL9vAi6oN?K97rPn?uR#b}=%h6B?Zr5GNefdH2ANOdDy-F?j++(Szf5 zdAe`vZ9_PsW2Eko&rQU#4-!2wO(-&f*X`n^ zrCq vDJR9;zvn^-B}-oU6R0wnKuSuP%K!0S#B`E6<@zpn00000NkvXXu0mjfVQ`x{ literal 0 HcmV?d00001 diff --git a/images/screen_320x460.png b/images/screen_320x460.png new file mode 100644 index 0000000000000000000000000000000000000000..172ec555c304fdcd2cd34c3f920af2cce1009406 GIT binary patch literal 12778 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 literal 0 HcmV?d00001 diff --git a/images/screen_57x57.png b/images/screen_57x57.png new file mode 100644 index 0000000000000000000000000000000000000000..e2085f29fc2ca0147251b8327d06081670943f0c GIT binary patch literal 1807 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 literal 0 HcmV?d00001 diff --git a/images/settings.png b/images/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..3ff91d37868c352010436758d80e5f4766a26fb5 GIT binary patch literal 2326 zcmXX|4LH+l8~-ygQWnypm(h+?*it^iNn4buIcr%g`B>XxZL_t_XHH3oMNSe)BZLd5 zVI7l1$#*&ll~Bk+3G~i;lQ`Rx5c#Cm@)8dDDXSwo#{DL(l`4e7TUtjNJ zYis*W1%~elw#iyn+}M8PfFfTx!@-Izv_4i)8Z)Krfp_juOdjFc!nfXV)urdEU$UV9 zY(qTOzXcAbLe;Mz;{N*m`tW9m-z|haAk2^U%NV6842kWKy1F{~;exInBl{5T4LUlVbgwvobrG{8Y;uNP?(X65Uwf2yva?-;hYK)%y`Sb4=)HSyZZ$E<)x6rcB9%)2 zNk~vP2B46ES$SJE{&Xw2epbWUZg>yQKv*ukj; zQdJ%abD|?-PXie-JDL_bMxI>@KfH zK!+VRR{B_iU2*H>DDM+gJ~gf*k@hO0SK=9WR$qtygz5knpRSgRM51gallgl? zAdEyJd76LtAz>R`yLK(HRc1mm(}+1{&KE>PENRq+O`Ur&%0uJziWtw)+B425@Wf(u zpyj>qZ#mA2xu&Kj`Az^D3rjp)P|tPHPHlx?6SH~OWr1V^IMp4gTO)A%S?%!|Ky*mz zDk^3;EpV;)*!7L*dkn+wulo$PY^kiM0CGEbs3tm?Bl9i5d$K>%`pMI3D|I+B2LkiK z0RRk(?2r&F^gn3dF47#3er(4Wcc$LFWfyeGr65i?-JgGH%g*=6t1rW!xHf5E^)Xr{6x zMB;qijxRS~hG^t(Ypprd!f4IQ%WG_jW z%=tAtHz#LhV~kQ_=^N6rOO#OTDf*}7mB`73&19BY|E)^ZjztW=d*F*IDlS&eE0%Wv zV*pE2{Yp^S4eSV(KIy@si%msku*X+4ipN?khr7C z!SICI8u7O_nvJ{p&9<`-ttmVA7wUtZ2~rsIbZ8ZUs#}#$#0hrsSbRxkck6~yBlA%jB8$EMrorO?RUs*Xz?$qYMfvzRbjVx3upph&$M~`k6&~OCDMk;BFO$jESNcpwqT#3C zc6Sp^wBnaeTWb^#S#`wrev3yyZ;M2vBS($|%_)`(xem((4}7b|p$P=D8xfj;rRk|o z=SP}!RR*d#tCKHo?Y_$%@Lj(6bf1I6Qf_Uyynd*shd(weYt8MKin6k@$W!wQXa92V zwox8T4SZp?OnsgOG``9wzgFfbD!HyT4b|o z03>GP!STp??#ysQ?lDhK|0ja4-Fko_l{K151XKac@$?~J2_93ddf#hTVNyO6--E#E z5=+vJQ(HCtMVn7`1vKr$fB*hH+GqJUAaHYY-Q3A_D%vrA>SfS}a6e4XUy;{k!;4B& zoUTn)@~P;v`n~(*4j0m=zxj9AJcIW@O$$+v?t6v-&^}fei$ 0 && v.h > 0) { + if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) { + console.log("here1:", + ((c.width < v.w) ? c.width : v.w), + ((c.height < v.h) ? c.height : v.h)); saveImg = c_ctx.getImageData(0, 0, (c.width < v.w) ? c.width : v.w, (c.height < v.h) ? c.height : v.h); @@ -406,6 +418,13 @@ that.getCleanDirtyReset = function() { 'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1}; return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes}; +}; + +that.absX = function(x) { + return x + viewport.x; +} +that.absY = function(y) { + return y + viewport.y; } @@ -454,11 +473,9 @@ that.clear = function() { if (conf.logo) { that.resize(conf.logo.width, conf.logo.height); - that.viewportChange(0, 0, conf.logo.width, conf.logo.height); that.blitStringImage(conf.logo.data, 0, 0); } else { that.resize(640, 20); - that.viewportChange(0, 0, 640, 20); c_ctx.clearRect(0, 0, viewport.w, viewport.h); } @@ -551,7 +568,15 @@ imageDataCreate = function(width, height) { }; rgbxImageData = function(x, y, width, height, arr, offset) { - var img, i, j, data; + var img, i, j, data, v = viewport; + /* + if ((x - v.x >= v.w) || (y - v.y >= v.h) || + (x - v.x + width < 0) || (y - v.y + height < 0)) { + //console.log("skipping, out of bounds: ", x, y); + // Skipping because outside of viewport + return; + } + */ img = c_imageData(width, height); data = img.data; for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { @@ -560,7 +585,7 @@ rgbxImageData = function(x, y, width, height, arr, offset) { data[i + 2] = arr[j + 2]; data[i + 3] = 255; // Set Alpha } - c_ctx.putImageData(img, x - viewport.x, y - viewport.y); + c_ctx.putImageData(img, x - v.x, y - v.y); }; // really slow fallback if we don't have imageData diff --git a/include/rfb.js b/include/rfb.js index 7cca8e60..ca8ec03d 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -118,7 +118,9 @@ var that = {}, // Public API methods /* Mouse state */ mouse_buttonMask = 0, - mouse_arr = []; + mouse_arr = [], + viewportDragging = false, + viewportDragPos = {}; // Configuration attributes Util.conf_defaults(conf, that, defaults, [ @@ -133,6 +135,8 @@ Util.conf_defaults(conf, that, defaults, [ ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'], ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'], + ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'], + ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'], ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'], @@ -547,7 +551,7 @@ function flushClient() { // overridable for testing checkEvents = function() { var now; - if (rfb_state === 'normal') { + if (rfb_state === 'normal' && !viewportDragging) { if (! flushClient()) { now = new Date().getTime(); if (now > last_req_time + conf.fbu_req_rate) { @@ -572,13 +576,43 @@ mouseButton = function(x, y, down, bmask) { } else { mouse_buttonMask ^= bmask; } - mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); + + if (conf.viewportDrag) { + if (down && !viewportDragging) { + viewportDragging = true; + viewportDragPos = {'x': x, 'y': y}; + + // Skip sending mouse events + return; + } else { + viewportDragging = false; + } + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); flushClient(); }; mouseMove = function(x, y) { //Util.Debug('>> mouseMove ' + x + "," + y); - mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); + var deltaX, deltaY; + + if (viewportDragging) { + //deltaX = x - viewportDragPos.x; // drag viewport + deltaX = viewportDragPos.x - x; // drag frame buffer + //deltaY = y - viewportDragPos.y; // drag viewport + deltaY = viewportDragPos.y - y; // drag frame buffer + viewportDragPos = {'x': x, 'y': y}; + + display.viewportChange(deltaX, deltaY); + + // Skip sending mouse events + return; + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); }; @@ -778,7 +812,6 @@ init_msg = function() { display.set_true_color(conf.true_color); display.resize(fb_width, fb_height); - display.viewportChange(0, 0, fb_width, fb_height); keyboard.grab(); mouse.grab(); @@ -1309,7 +1342,6 @@ encHandlers.DesktopSize = function set_desktopsize() { fb_width = FBU.width; fb_height = FBU.height; display.resize(fb_width, fb_height); - display.viewportChange(0, 0, fb_width, fb_height); timing.fbu_rt_start = (new Date()).getTime(); // Send a new non-incremental request ws.send(fbUpdateRequests()); diff --git a/include/ui.js b/include/ui.js index fa41854c..82f7d963 100644 --- a/include/ui.js +++ b/include/ui.js @@ -12,6 +12,7 @@ var UI = { +rfb_state : 'loaded', settingsOpen : false, connSettingsOpen : true, clipboardOpen: false, @@ -36,8 +37,8 @@ load: function() { // Settings with immediate effects UI.initSetting('logging', 'warn'); WebUtil.init_logging(UI.getSetting('logging')); - UI.initSetting('stylesheet', 'default'); + UI.initSetting('stylesheet', 'default'); WebUtil.selectStylesheet(null); // call twice to get around webkit bug WebUtil.selectStylesheet(UI.getSetting('stylesheet')); @@ -55,6 +56,7 @@ load: function() { UI.rfb = RFB({'target': $D('noVNC_canvas'), 'onUpdateState': UI.updateState, 'onClipboard': UI.clipReceive}); + UI.updateSettingsState(false); // Unfocus clipboard when over the VNC area //$D('VNC_screen').onmousemove = function () { @@ -66,9 +68,15 @@ load: function() { // Show mouse selector buttons on touch screen devices if ('ontouchstart' in document.documentElement) { + // Show mobile buttons $D('noVNC_mobile_buttons').style.display = "inline"; UI.setMouseButton(); - window.scrollTo(0, 1); + // Remove the address bar + setTimeout(function() { window.scrollTo(0, 1); }, 100); + UI.initSetting('clip', true); + $D('noVNC_clip').disabled = true; + } else { + UI.initSetting('clip', false); } //iOS Safari does not support CSS position:fixed. @@ -76,11 +84,21 @@ load: function() { if ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) { - UI.setOnscroll(); - UI.setResize(); + //UI.setOnscroll(); + //UI.setResize(); } $D('noVNC_host').focus(); + + UI.setViewClip(); + Util.addEvent(window, 'resize', UI.setViewClip); + + Util.addEvent(window, 'beforeunload', function () { + if (UI.rfb_state === 'normal') { + return "You are currently connected."; + } + } ); + }, // Read form control compatible setting from cookie @@ -166,7 +184,6 @@ initSetting: function(name, defVal) { clickSettingsMenu: function() { if (UI.settingsOpen) { UI.settingsApply(); - UI.closeSettingsMenu(); } else { UI.updateSetting('encrypt'); @@ -177,6 +194,7 @@ clickSettingsMenu: function() { UI.updateSetting('cursor', false); $D('noVNC_cursor').disabled = true; } + UI.updateSetting('clip'); UI.updateSetting('shared'); UI.updateSetting('connectTimeout'); UI.updateSetting('stylesheet'); @@ -195,32 +213,16 @@ openSettingsMenu: function() { if (UI.connSettingsOpen == true) { UI.connectPanelbutton(); } - $D('noVNC_Settings').style.display = "block"; + $D('noVNC_settings').style.display = "block"; UI.settingsOpen = true; }, // Close menu (without applying settings) closeSettingsMenu: function() { - $D('noVNC_Settings').style.display = "none"; + $D('noVNC_settings').style.display = "none"; UI.settingsOpen = false; }, -// Disable/enable controls depending on connection state -settingsDisabled: function(disabled, rfb) { - //Util.Debug(">> settingsDisabled"); - $D('noVNC_encrypt').disabled = disabled; - $D('noVNC_true_color').disabled = disabled; - if (rfb && rfb.get_display() && rfb.get_display().get_cursor_uri()) { - $D('noVNC_cursor').disabled = disabled; - } else { - UI.updateSetting('cursor', false); - $D('noVNC_cursor').disabled = true; - } - $D('noVNC_shared').disabled = disabled; - $D('noVNC_connectTimeout').disabled = disabled; - //Util.Debug("<< settingsDisabled"); -}, - // Save/apply settings when 'Apply' button is pressed settingsApply: function() { //Util.Debug(">> settingsApply"); @@ -229,6 +231,7 @@ settingsApply: function() { if (UI.rfb.get_display().get_cursor_uri()) { UI.saveSetting('cursor'); } + UI.saveSetting('clip'); UI.saveSetting('shared'); UI.saveSetting('connectTimeout'); UI.saveSetting('stylesheet'); @@ -237,6 +240,7 @@ settingsApply: function() { // Settings with immediate (non-connected related) effect WebUtil.selectStylesheet(UI.getSetting('stylesheet')); WebUtil.init_logging(UI.getSetting('logging')); + UI.setViewClip(); //Util.Debug("<< settingsApply"); }, @@ -257,65 +261,60 @@ sendCtrlAltDel: function() { }, setMouseButton: function(num) { - var b, blist = [1,2,4], button, - mouse = UI.rfb.get_mouse(); + var b, blist = [0, 1,2,4], button; if (typeof num === 'undefined') { - // Show the default - num = mouse.get_touchButton(); - } else if (num === mouse.get_touchButton()) { - // Set all buttons off (no clicks) - mouse.set_touchButton(0); - num = 0; - } else { - // Turn on one button - mouse.set_touchButton(num); + // Disable mouse buttons + num = -1; + } + if (UI.rfb) { + UI.rfb.get_mouse().set_touchButton(num); } for (b = 0; b < blist.length; b++) { button = $D('noVNC_mouse_button' + blist[b]); if (blist[b] === num) { + button.style.display = ""; + } else { + button.style.display = "none"; + /* button.style.backgroundColor = "black"; button.style.color = "lightgray"; - } else { button.style.backgroundColor = ""; button.style.color = ""; + */ } } }, updateState: function(rfb, state, oldstate, msg) { - var s, sb, c, cad, klass; + var s, sb, c, d, cad, vd, klass; + UI.rfb_state = state; s = $D('noVNC_status'); sb = $D('noVNC_status_bar'); c = $D('connectPanelbutton'); - cad = $D('sendCtrlAltDelButton'); + d = $D('disconnectButton'); switch (state) { case 'failed': case 'fatal': - c.disabled = true; - cad.style.display = "none"; - UI.settingsDisabled(true, rfb); + c.style.display = ""; + d.style.display = "none"; + UI.updateSettingsState(false); klass = "noVNC_status_error"; break; case 'normal': - c.value = "Disconnect"; - c.onclick = UI.disconnect; - c.disabled = false; - cad.style.display = ""; - UI.settingsDisabled(true, rfb); + c.style.display = "none"; + d.style.display = ""; + UI.updateSettingsState(true); klass = "noVNC_status_normal"; break; case 'disconnected': $D('noVNC_logo').style.display = "block"; - c.value = "Connection"; - c.onclick = UI.connectPanelbutton; case 'loaded': - c.value = "Connection"; - c.onclick = UI.connectPanelbutton; - c.disabled = false; - cad.style.display = "none"; - UI.settingsDisabled(false, rfb); + //c.value = "Connection"; + c.style.display = ""; + d.style.display = "none"; + UI.updateSettingsState(false); klass = "noVNC_status_normal"; break; case 'password': @@ -325,15 +324,15 @@ updateState: function(rfb, state, oldstate, msg) { $D('noVNC_connect_button').onclick = UI.setPassword; $D('noVNC_password').focus(); - c.disabled = false; - cad.style.display = "none"; - UI.settingsDisabled(true, rfb); + c.style.display = "none"; + d.style.display = ""; + UI.updateSettingsState(false); klass = "noVNC_status_warn"; break; default: - c.disabled = true; - cad.style.display = "none"; - UI.settingsDisabled(true, rfb); + c.style.display = "none"; + d.style.display = ""; + UI.updateSettingsState(false); klass = "noVNC_status_warn"; break; } @@ -346,6 +345,40 @@ updateState: function(rfb, state, oldstate, msg) { }, +// Disable/enable controls depending on connection state +updateSettingsState: function(connected) { + + //Util.Debug(">> updateSettingsState"); + $D('noVNC_encrypt').disabled = connected; + $D('noVNC_true_color').disabled = connected; + if (UI.rfb && UI.rfb.get_display() && + UI.rfb.get_display().get_cursor_uri()) { + $D('noVNC_cursor').disabled = connected; + } else { + UI.updateSetting('cursor', false); + $D('noVNC_cursor').disabled = true; + } + $D('noVNC_shared').disabled = connected; + $D('noVNC_connectTimeout').disabled = connected; + + if (connected) { + UI.setViewClip(); + UI.setMouseButton(1); + $D('sendCtrlAltDelButton').style.display = "inline"; + $D('noVNC_view_drag_button').style.display = "inline"; + } else { + UI.setMouseButton(); + $D('sendCtrlAltDelButton').style.display = "none"; + $D('noVNC_view_drag_button').style.display = "none"; + } + + // State change disables viewport dragging. + // It is enabled (toggled) by direct click on the button + UI.setViewDrag(false); + //Util.Debug("<< updateSettingsState"); +}, + + clipReceive: function(rfb, text) { Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "..."); $D('noVNC_clipboard_text').value = text; @@ -412,6 +445,7 @@ clipSend: function() { showClipboard: function() { //Close settings if open if (UI.settingsOpen == true) { + UI.settingsApply(); UI.closeSettingsMenu(); } //Close connection settings if open @@ -428,31 +462,66 @@ showClipboard: function() { } }, +setViewClip: function(clip) { + var display, cur_clip, pos, new_w, new_h; + + if (UI.rfb) { + display = UI.rfb.get_display(); + } else { + return; + } + + cur_clip = display.get_viewport(); + + if (typeof(clip) === 'undefined') { + // Nothing + clip = UI.getSetting('clip'); + } + + if (clip && !cur_clip) { + // Turn clipping on + UI.updateSetting('clip', true); + } else if (!clip && cur_clip) { + // Turn clipping off + UI.updateSetting('clip', false); + display.set_viewport(false); + $D('noVNC_canvas').style.position = 'static'; + display.viewportChange(); + } + if (UI.getSetting('clip')) { + // If clipping, update clipping settings + $D('noVNC_canvas').style.position = 'absolute'; + pos = Util.getPosition($D('noVNC_canvas')); + new_w = window.innerWidth - pos.x; + new_h = window.innerHeight - pos.y; + display.set_viewport(true); + display.viewportChange(0, 0, new_w, new_h); + } +}, + +setViewDrag: function(drag) { + var vmb = $D('noVNC_view_drag_button'); + if (!UI.rfb) { return; } + + if (typeof(drag) === "undefined") { + // If not specified, then toggle + drag = !UI.rfb.get_viewportDrag(); + } + if (drag) { + vmb.style.backgroundColor = "black"; + vmb.style.color = "lightgray"; + UI.rfb.set_viewportDrag(true); + } else { + vmb.style.backgroundColor = ""; + vmb.style.color = ""; + UI.rfb.set_viewportDrag(false); + } +}, showKeyboard: function() { - //Get Current Scroll Position - var scrollx = - (document.all)?document.body.scrollLeft:window.pageXOffset; - var scrolly = - (document.all)?document.body.scrollTop:window.pageYOffset; - - //Stop browser zooming on textbox. - UI.zoomDisable(); - $D('keyboardinput').focus(); - scroll(scrollx,scrolly); - //Renable user zoom. - UI.zoomEnable(); + $D('keyboardinput').focus(); }, -zoomDisable: function() { - //Change viewport meta data to disable zooming. - UI.changeViewportMeta("user-scalable=0"); -}, - -zoomEnable: function(){ - //Change viewport meta data to enable user zooming. - UI.changeViewportMeta("user-scalable=1"); -}, changeViewportMeta: function (newattributes) { @@ -505,6 +574,7 @@ setBarPosition: function() { connectPanelbutton: function() { //Close connection settings if open if (UI.settingsOpen == true) { + UI.settingsApply(); UI.closeSettingsMenu(); } if (UI.clipboardOpen == true) { diff --git a/include/mobile.css b/tests/viewport.css similarity index 100% rename from include/mobile.css rename to tests/viewport.css diff --git a/tests/viewport.html b/tests/viewport.html index fbbf286d..1fb0a793 100644 --- a/tests/viewport.html +++ b/tests/viewport.html @@ -1,7 +1,7 @@ Viewport Test - + - + + + + + + + + @@ -28,13 +35,6 @@ - - - - - - - -
- - - - - - - - +
+ +
+ + + + + + +
- - - + + + +
- @@ -94,13 +111,14 @@
-
+
  • Encrypt
  • True Color
  • Local Cursor
  • +
  • Clip to window
  • Shared Mode
  • Connect Timeout (s)

  • @@ -143,13 +161,15 @@
    Loading
-

no
VNC

- - Canvas not supported. - - - + + +
+ + Canvas not supported. + +
+