From b20498caa4d6312339026e0b9881b5409978d4c5 Mon Sep 17 00:00:00 2001 From: Tom Marshall Date: Fri, 23 Dec 2011 15:28:12 -0800 Subject: [PATCH] Initial import --- .gitignore | 2 + AndroidManifest.xml | 18 + ant.properties | 17 + build.xml | 85 +++ main.xml | 7 + proguard.cfg | 40 ++ project.properties | 11 + res/drawable-hdpi/ic_menu_compose.png | Bin 0 -> 2324 bytes res/drawable-hdpi/ic_menu_delete.png | Bin 0 -> 2029 bytes res/drawable-hdpi/ic_menu_discard.png | Bin 0 -> 3698 bytes res/drawable-hdpi/ic_menu_edit.png | Bin 0 -> 2399 bytes res/drawable-hdpi/ic_menu_revert.png | Bin 0 -> 2093 bytes res/drawable-hdpi/ic_menu_save.png | Bin 0 -> 2050 bytes res/drawable-hdpi/icon.png | Bin 0 -> 4147 bytes res/drawable-ldpi/icon.png | Bin 0 -> 1723 bytes res/drawable-mdpi/icon.png | Bin 0 -> 2574 bytes res/menu/options_menu.xml | 20 + res/values/strings.xml | 9 + src/tdm/xserver/ByteQueue.java | 152 +++++ src/tdm/xserver/ClientView.java | 129 ++++ src/tdm/xserver/FontData.java | 141 +++++ src/tdm/xserver/FontDataNative.java | 36 ++ src/tdm/xserver/FontDataPCF.java | 636 ++++++++++++++++++++ src/tdm/xserver/MathX.java | 18 + src/tdm/xserver/UIHandler.java | 104 ++++ src/tdm/xserver/X11Atom.java | 99 ++++ src/tdm/xserver/X11CharInfo.java | 11 + src/tdm/xserver/X11Client.java | 320 ++++++++++ src/tdm/xserver/X11Color.java | 19 + src/tdm/xserver/X11Colormap.java | 124 ++++ src/tdm/xserver/X11Cursor.java | 103 ++++ src/tdm/xserver/X11Depth.java | 14 + src/tdm/xserver/X11Drawable.java | 314 ++++++++++ src/tdm/xserver/X11Error.java | 54 ++ src/tdm/xserver/X11ErrorMessage.java | 57 ++ src/tdm/xserver/X11Event.java | 40 ++ src/tdm/xserver/X11EventMessage.java | 76 +++ src/tdm/xserver/X11Font.java | 103 ++++ src/tdm/xserver/X11FontProp.java | 7 + src/tdm/xserver/X11Fontable.java | 14 + src/tdm/xserver/X11Format.java | 332 +++++++++++ src/tdm/xserver/X11GContext.java | 179 ++++++ src/tdm/xserver/X11Keyboard.java | 279 +++++++++ src/tdm/xserver/X11Message.java | 21 + src/tdm/xserver/X11Pixmap.java | 51 ++ src/tdm/xserver/X11Point.java | 7 + src/tdm/xserver/X11Pointer.java | 15 + src/tdm/xserver/X11Property.java | 30 + src/tdm/xserver/X11Protocol.java | 103 ++++ src/tdm/xserver/X11Rect.java | 22 + src/tdm/xserver/X11ReplyMessage.java | 39 ++ src/tdm/xserver/X11RequestMessage.java | 195 ++++++ src/tdm/xserver/X11Resource.java | 34 ++ src/tdm/xserver/X11Screen.java | 148 +++++ src/tdm/xserver/X11Selection.java | 9 + src/tdm/xserver/X11Server.java | 425 +++++++++++++ src/tdm/xserver/X11SetupRequest.java | 47 ++ src/tdm/xserver/X11SetupResponse.java | 72 +++ src/tdm/xserver/X11Visual.java | 28 + src/tdm/xserver/X11Window.java | 790 +++++++++++++++++++++++++ src/tdm/xserver/XServer.java | 94 +++ 61 files changed, 5700 insertions(+) create mode 100644 .gitignore create mode 100644 AndroidManifest.xml create mode 100644 ant.properties create mode 100644 build.xml create mode 100644 main.xml create mode 100644 proguard.cfg create mode 100644 project.properties create mode 100644 res/drawable-hdpi/ic_menu_compose.png create mode 100644 res/drawable-hdpi/ic_menu_delete.png create mode 100644 res/drawable-hdpi/ic_menu_discard.png create mode 100644 res/drawable-hdpi/ic_menu_edit.png create mode 100644 res/drawable-hdpi/ic_menu_revert.png create mode 100644 res/drawable-hdpi/ic_menu_save.png create mode 100644 res/drawable-hdpi/icon.png create mode 100644 res/drawable-ldpi/icon.png create mode 100644 res/drawable-mdpi/icon.png create mode 100644 res/menu/options_menu.xml create mode 100644 res/values/strings.xml create mode 100644 src/tdm/xserver/ByteQueue.java create mode 100644 src/tdm/xserver/ClientView.java create mode 100644 src/tdm/xserver/FontData.java create mode 100644 src/tdm/xserver/FontDataNative.java create mode 100644 src/tdm/xserver/FontDataPCF.java create mode 100644 src/tdm/xserver/MathX.java create mode 100644 src/tdm/xserver/UIHandler.java create mode 100644 src/tdm/xserver/X11Atom.java create mode 100644 src/tdm/xserver/X11CharInfo.java create mode 100644 src/tdm/xserver/X11Client.java create mode 100644 src/tdm/xserver/X11Color.java create mode 100644 src/tdm/xserver/X11Colormap.java create mode 100644 src/tdm/xserver/X11Cursor.java create mode 100644 src/tdm/xserver/X11Depth.java create mode 100644 src/tdm/xserver/X11Drawable.java create mode 100644 src/tdm/xserver/X11Error.java create mode 100644 src/tdm/xserver/X11ErrorMessage.java create mode 100644 src/tdm/xserver/X11Event.java create mode 100644 src/tdm/xserver/X11EventMessage.java create mode 100644 src/tdm/xserver/X11Font.java create mode 100644 src/tdm/xserver/X11FontProp.java create mode 100644 src/tdm/xserver/X11Fontable.java create mode 100644 src/tdm/xserver/X11Format.java create mode 100644 src/tdm/xserver/X11GContext.java create mode 100644 src/tdm/xserver/X11Keyboard.java create mode 100644 src/tdm/xserver/X11Message.java create mode 100644 src/tdm/xserver/X11Pixmap.java create mode 100644 src/tdm/xserver/X11Point.java create mode 100644 src/tdm/xserver/X11Pointer.java create mode 100644 src/tdm/xserver/X11Property.java create mode 100644 src/tdm/xserver/X11Protocol.java create mode 100644 src/tdm/xserver/X11Rect.java create mode 100644 src/tdm/xserver/X11ReplyMessage.java create mode 100644 src/tdm/xserver/X11RequestMessage.java create mode 100644 src/tdm/xserver/X11Resource.java create mode 100644 src/tdm/xserver/X11Screen.java create mode 100644 src/tdm/xserver/X11Selection.java create mode 100644 src/tdm/xserver/X11Server.java create mode 100644 src/tdm/xserver/X11SetupRequest.java create mode 100644 src/tdm/xserver/X11SetupResponse.java create mode 100644 src/tdm/xserver/X11Visual.java create mode 100644 src/tdm/xserver/X11Window.java create mode 100644 src/tdm/xserver/XServer.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..987d083 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/** +gen/** diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..f203d8f --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/ant.properties b/ant.properties new file mode 100644 index 0000000..ee52d86 --- /dev/null +++ b/ant.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..7995b73 --- /dev/null +++ b/build.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/main.xml b/main.xml new file mode 100644 index 0000000..f02c9fd --- /dev/null +++ b/main.xml @@ -0,0 +1,7 @@ + + + + diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 0000000..b1cdf17 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..f049142 --- /dev/null +++ b/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-10 diff --git a/res/drawable-hdpi/ic_menu_compose.png b/res/drawable-hdpi/ic_menu_compose.png new file mode 100644 index 0000000000000000000000000000000000000000..bc153fac0f710309ed366cfc433ee0260df1cf81 GIT binary patch literal 2324 zcmZ{lX*3&%7RO^3r7~$cty08N)E3($_N7!ps~U>hCP73>tg$s(q-YG)1htJ?YHMkW z(4etQ)hKNy#nf6$Q6)&!P)pSqeeaxi-uv)A-1|TG{_g+WkNZ6#|<}796p{%YIZD87$kRW8~=qbj#^^1nBz$;*0MCJ9nW;i=7?? z$8HQ_{bVYD)uB6ystcCs1^VLlmouwel@(=&bZyX&TM9RnDs*0g5b2?=`Ur)xle*}1 z%Eq3i?l)u`!tD~_UTAjLs`HHH7`P8p_1i}i6?=Uz{I2jWa@r_hO0?)o+G-xfzE@tdO-dZ_dq_7e zV}Sp#l@aOtUbovZ(fj_QPQ>aD)`pwXOM(_QBEFZUxhH;MhC!rrl~z1nw5(k}sKkuG zFa_#&uz<*n&1&i4ov6A`#kCx!#Iya5E5qB>3qxl!Gl~-Y1aTkSLL%8CD~<;Y)J2xa zRqfOb{={}V{Ow7SkX^7Sre<^G-T`CMclUGumo|AzRzNw?o%3_)qHGqo-<~=sX>%v4 z#Md%#&5@Avh87*^v5?z!pH%Ba(~~^&DaZ;&29 zRhdK3i9Zpnp}jCbK6z&63)g7}^R!7BM)i4J`_@OFJ9Y^#U*GTW)(I4 zq;EXe`ykz*A_j4c>uY-Ut0%TR^TWH2c^GnYj0`;ClCQUdvn9X*TC?%dpBPvlwdZFy z2_F^RWPTK(egG(*)@$DJ~Yv9R+T^6}}ii2=VVLqw@N`Mb5+9(Y?v{~Qb1 zEks(|oNU?ToqC4(xQplC^fThZ!4^mrK!Bf{d59rxL7CX~LjJTz?q7N*u#}{T)%OJU*7kVxp zEu?oB?7Qp^XmMSzj#B8hQe4+hyMyKUYhec~ftN0y2}XshMvG)Sr-Xjnqe=}{&3#?A z^0zDLi~YJ)(`B&4Go4Po$!y0q)gT6rI~`k^-Vg(bTg^!Ra`ZH}fEjjU`14=gr*F07 zx4TaWH_Y$WCwwj<$K|DE!;o89>Y*tYTyIN?9^7ya>X|Jz<%igkS!131pYWfvOx0qY8tu;+(?#CUa%lrd--s=0fzWwD`d&BW+t6BN%M7jkeILoF=7MM#v2pmY5lnJlGL_Mcv0?uzp_{6U2KiY}%HwQ4qT> zoTS0GO%LSsASKQ`wdf>BE2t8G5bH|3b+2LX-`R@(YWXfvAiX>@>zznk+F|~ylI%Q4 z0ay~w#6J>uC;)w!9!wjC(AG0_)i*FPFgAf1Xv5$pFqr@KS%v>G5W@lpf${&}09LzN zacGeK{vnVMLc&Fw_=QAA0$@6NE+l-yAv*eBC?w1e7X>hkekiVVNMybfm#{!mEH)Ac hunstciYOu$k3-|I0r3(2IJ3he0LsqE_Nfhq@(*hrIp_cY literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_delete.png b/res/drawable-hdpi/ic_menu_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..ce5ecc48a161a5a4852cd3f28b205e3a91a780b0 GIT binary patch literal 2029 zcmZ{ldpr{g8^`C8a=ev$iKwj$t>v;UmpbmT)xnsQCYP~E%-D>%X3ZhFge1msC(~Jo z^19|;uCs_E>_qNiX`~5z=l$dTyzd|HAJ6mqJkR&}eV#vm@hDsCgTEa61pojXL?SF5 zM2gs}1CpXHoC)s}i8%I}wIu*ho&?epP0|4fXY$_3zssuI*LZIk9AM`CpXEU4*L?th zR14D5%<&F)B`Y$~<+@VKz4_*ftjU$-;NIZc5?W_PxhV&@wSUkuX#`+~RIq`-Ig)R7 zTQXtAaE>M;IzHan{FJ-jDU`xzDFs-%?E7dpF^sX|xgT-|7E3Y8A9om&S)shi@NG|g zi1+J;YJTjCwMT}+Wo}M5PtZhW*UdNT52hvu6@!iogvz036DYr4Bpk8iT#r_UF?`tj z*2Y`qa;;2*Gn=^4l9XJgPJu>Eg~2*fa>q@ul^^fg3Ruj~ut$o?TVD27p zXI4p@YY_SB%dNR40asykJ4$GxzG>|F#9FW%CZSd9@ZD~vzfxFp@hpKQDFuq_MH&#$qN38f;BjpRax)Ys8H9Hw*2;%iwTs^E|-ZZ3|EI1@5 zJDigPwsP99Me5nPJFXB<%*Ztgd~>L4;!KQBD17V6=;_eUiu9Bvay{8;+W1U%7?LqH z(aN4NnQNYWwu)^y0Hn^qIv$%3UQ5;O>3*+8wTxLpDy~D>2A!75l=9>n7}b4~uKz52 z9idQd^XvM{zD4qQ>1y*rPk$zZqjpm2T6{{c`y!)DR?Z_Jy&LNCj9h$kAhz?;TOmp? zs`~!Va@f8ePQTyObjW5Is_E&$`<(@nR9Nhz)=#K(z)(IK+jC3yo@Y)%rk_cy8aXD& zNAn}`*6>(R)=xvSER3|o6SP&CO9x+1Y5fjt!< z{exC;+uqbOK{D5{d*C~bE35+lutMI1lFNq(5_IwBG1lyloSd-!y=edRouP$|w{86SD+22ZzTJx(b61FI)S_VtJ0rSgt>5aZ2$Bv2cZA$FaPgxTe(V17vEB-(jixRhfZ{9Kk67<5b>bhgNZO zcXLojho-Eolo-+m6DgFvSVQfDh&#u`l|wRtf^RK5k=lJO@U@Mx#%kIqe|u0)TSeGb z=c@pAjq>kp`u(0=ldzwdQSYb}C(e4E3=uap4K#3gm1aBfU83||(gZe+a4ekDp0}l? zcA;yul*vzn)*f$ro46eKlzF$S)JP#EO&)zR`0G+jINqCO-FDfr>ziGdH<`>?FoQ4_6;nQ)y?Je?tr8?zmdDl@2Y61KWh zPjq@9kqF&#YQ2Gz#e-=C5~-C>eFX|u@zg|j4&#XHG9W`cw<~3NlXo?74+{9HY8x8I zC-fuHI5@l-aD7wB}I3jkZRXZt?O+xn)nZc(d z27B_LlqYo?&qu_{jAIo~E0q8f;vwhpnT3fGYg_7Lq!XN>-0~&!n;LxojT~~>x$>gM zS$5T+3{K+iTB&ROf?|o+<)&3aVW_q_qU!r&et>ne`HAGTkcJ`swRxKxa~Mre2wG9D zK<{fOPs#P_i=b>!M{Q%^5O;AkU>p4F1MLp_Pu_%(wAvnADdY7=w798~BuGq^-(8E2 zsMq~O$Pc22&{6pb9SI9O+veJQO>2lab}lSJOHtU(cA2u>bm@Hu|Leeqe#a@wl{S zn|-D};Bk!PT_({%eab9K_3{R%wwd9I7Jm3saLzg{AYy^0DhvxG9S*-Rqph*x61c)5 zti6lLHcb2qsUBZP3>Uif8kH2;P8i&%VDrq!h)MNddimf(|Gz>0taFjbaCq+n-p4NpM>4|tQAhxt3%cka?+6h(^k2x2h{cfsFiP10 vpopmK5i}7W6yia`0nEHa@C1_J;f-^^d3c2ecH=IKjsVCjww5o<-S7VkfcMI5 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_discard.png b/res/drawable-hdpi/ic_menu_discard.png new file mode 100644 index 0000000000000000000000000000000000000000..4683f61935a92132db1aa9f9394931f24840c8c8 GIT binary patch literal 3698 zcmZ{nS2!Gw(#CfYR=0XvWrb{_L|MJFYW#JAAP83Pos) zo~R)JfXW0e;k%01L*3N-@5p~bb;w!nZ`0315&3^jOyI%2yL~De2t`8&+%Ho{pEQ-W zm$=IPojsrTnF@n_Z1=6f>zF#rX0B{$f_MTjp`t9=wM_&FpLMM$Iu@DB1ws%&m+<=T z5h7{bsDMO$qt;_ghz*>gsjU%S3YAn|XLsfKpFt(0wy;J|_Vd6ioq{XeT1Crtbx;+F zgQ`f*M7;eJo^0+FXGoMZNEaOH@kx@}HRYsszuyVa8jxo{NXx{d)EyB+E2-S2-D;$N zi+4g$7h}(&Ja+BMSKn9PClN8H>)re@=27XpH$3FCLS%$5y3EJM0a3CARGZuJWgtWq zOksUVj3Mo(->Zf{c7M_%s*=JKs<5eMG4v9D zk9KEQYy>BTKT4f<3N=^gL(>c^$^$CA@`A=v+e<*1Le%g0*jJBCe7Q5=S|?~!X1qVm1nz4ZE8IG8kFgcJSdRYTPP~ z**YlexB1u>!wgm!mFgE$#?6Bd!)B`dxp)4#NfN!fQOQX@o7@At%+3)hKRBQ8JRIia z&71n2)jRIfeQ(st;~!E$)~W$r^&StUWarKtEHIxvYdmUDp=+j;!MDAgx9mlS9b=OX z`hx~YcnJ6)7ZeU5RN&w-Mxb8PLi;KY)+jZuR@OTzmO0KpKcE!0y_txtKTNz{%#Z#o zkO5bUG$nQn=VHuoerfl6tOH8gV;n(;JgwZ+V>WU#R^YAN;=|X)1{QM!Uu>$pM&xcx zo(73-U)K>&jA9)E_jmhaTw~)uG?4@P?!W2)RV(#ntd$T4&L1HAhL_112W81bGh-METlU9{!V)<@(Ufzhj4)%Nbvy2|72@08clO<+?-hnollyi4Fngw{AJ``v4g zC#RQP*=8|H&IZn zL_=}5mahp-OrNJq($f;3oNcJ$uGHCj=ueMAe=D2h2FPxV2y>;U!k$=8c=M$H)|1cl z3PF=61!%-S3SMv2ZiY5hW;5FsNgVBE4sCP`i})6AjQi1Ua`${i&r?+8P>5J%yttfs zvNd3gE7T{lxK(!fA#sH45WJ~7MP7+Onk}>TzD6s%?4BkW5V`6;lbmM^CLS3((sTt_ zd6_J^{SF}}IXRPx`hWqQQtg}^sqcL6@}s~OIgL&0$EI#izrJF2pZrRBa?2?urhPe> z9Ec{|xH-=z&6Sd^D+})=S8-TtO_jM=P24$zt4ax2!~Cams&+wGL)6;_9Pb|IA49d8 zX)x!Tf-+8d#gqD!4AFNHv}=R8%vPFDC(SbV5@WUsBW%IpXPBP3R)Y;~@(-^2@j%kI z5JN4ktv$!;NxI1?fUt_>$gt}hRA~aD_IHHifq_Nqb0U<%1yG~z%WSTFB*|?0Be(PU z_6##v4{|`w--Zg`3Ox2}S0!5|``7NoD4tcQl;?&+Eit0JY0BTd|JSv2A+(`yz3~h8 z5>c)bE6V%AEGeou0fJ~7phEqwmAwfps>MCwt@X`Y5)d(QCd8-hw&$r@?#o|HV%x#m`wNsF3QQs%l z4=fe*%@axQB~wo1=&6yRg|GJ8Bel5)_^H) z)hd1T`oN{MH;lu3vrsW&Z78+eS)aw%8*XKE+R9AN2cQpi0X#Si2D~@jRtGy}co-~L zEppvA)G#w@TVJ>=3@Xx7d-wVQxU_<=QAZ&;78*)jV$E$?1UahAkv+C21bbJ5geJz~ zXGLl&AxGS1G4M11EEYuVv|J4w{FdQ#v5@j1Q5OQl zAA)(3AJLj;q)~GF6Y?J;F(QOv3$EzmqTm(i39RUVe@e7C+O;jHHwuWTW6d2J0xAk3XA4^O9AXKN4*zt_iIEkp1bA{HjqgmEF$*#Zj>aghgv2c5_-n ziGcm_>;0n3bbI6JExt6gp2YFmK|?Ob!cEt;#5sTal%{j|d@0zGOQc|iR_Kd`Qytc9 zw)EP##dH5<0ms*)k)=ox2nEV1gQ(i{I=hV0J!{^JN;!mLl@jo_$C#vuND0%SxdSv23M$C;5woPT@e=QOiXI?ndXz<1?`2QdqHYA!%V^K{2ptqa!v+R<&F&uxh8fln*^0|TqgDP6NG z;x!^{OU)e^#_oyp5a-XU&5Y7}0cD;si6#wFbluC|S-R6KMCiJE9;+<}F?tSg)GgxG zPMw2q^jE5nH5wtaN|G!4CCIjxs+A6-M9JAoomDOxv2G4FBMq4_cB9A9#J6-z?6b(x zq3Rb&&7n}J(BJ^IRBumEpn3>($k^%FqnwF~Z2$$ps%L0mHSmWqBk4Q+ zReYUga@JCK>$XYV^M^1*CF4`@PxkbTLw<*@Ljy$Q@so70mV9Qc6c!>y}dd}5a5fKWPQ9=sN z-i+ld*nBi<7Z|Tpr%jf3SlhZKz3#iIo~KL;#!pH+qn3?_B{2b8sGBIMGDbX*{*X@Y zah3(IpiawMAPRjUzDr*b^z6J(Hu8|0epcB&OnIt#G-L9> z{3Jd{aA#0F_d@2`57(e|W6iqj-MeR;vmxG#R-HGg&AHhiRx2w<5Ry`;*l- zN$)~JBF$%dAUPLb3W9A=W&#h`&*L3EC%8?cu*qapDfemoBo;#=TFuLu+kUu*jw_P&@NWO>vdRGbP;b_f6V>XaLy4kejx(zs&Ef8VK9Yv6J$;YM ztStvh%m7e?QM;^VD+JAqsnt5N=a!w~3yv=lnm_Ge%5`DHoI0d{kB;6wKxB_^{aWzD zYb8;Za8;VN#Dyh1)kzg09iS=K@YT~3ij&82^@W9n0CjbBMvv82ogWwp?f0wzQ zC>lYwU$0!003P5&s}R10m11U)M3VnQ4Z)&t{_yIsYT$9%Wq7vxyq_hb56%LQjLzFJ z9%_yVG026}xdSCs+Y>~a*bXp~UnP~<53pJ72`>yE&ckKK-#Lp>>qXP7GWdJGQFiYu zi^EOfS;{8M4yrEW&%7K|TXbJk0}oe_h2r~BL@`*fV{HHP!oZ8g{;)Sb_xbh(J?wG! zU2*%UnE2RQ``Agn@UpuTfUuyDAitmlzYyF|SVT%hQc6&mUr=01P_UnTyXwCTuI{#u z_JRM~(5S1=aA%^KIb|AofHdm12(WA7ytk^BST#zO$~nq z(*;^SUyd542?%XspbG%C8R9Tn#q4irAM|VF-=RNrU;S4z)L+Zz{5<1&8#frkels~XL4Cw74$#7 zR)c2Y^S2d7jX`vGb|;amnGARkXkwdV6a?G>0oZoPW5X`!fzxr`X^f_RQgG?$m*p~m zK>D-Wgb-;SYe&&>`SN#X=MX{C1(mpQU^mk?Ik8g?vE_RFitoS4ZWv_b#QJhFaIW(X^M+$~@vcIf^E3r|!WoL-0MH zZ%ZV#NV~ZV9}15}H0wf>;C_siqgq^Gsplxn4Ch^A;{Het|HJ3u8|l=rRP6rib?%O3 z@7JNvZ;;}}WXpdH2RGP-v1yTxZnFc1Fy=p~q{e6G&9SC?ncRBhc#dxuvswD>jDrj( zhQ;niF_D9n$u<(@jen*wCo$x0ntmrC<(+2drZp*2TJjx;47GB@vCK(vY5%#5FFh{Zwe&IW4H->9cNv3Y)d?cVx zOa2ykD>ZemsoiRKrp`~USB^TCUJ}MTvKA52fd1gvSSNFXl))`qDU0=yJ#0gBNo&9H z4l?R0Xq18YhMgUxkdj7@tUP620xY}m5s1EinSv@W@pC9CU(1U)*mqFOJTzA%f<4dR zDKje|5d6m@(Iw7nLou3JuFL}?h|+))wP*ZdBM1@aPJ@!fSfaKM^1D9DPr7T<@#-uUxs)WG0LDCFZQCzY>;6ihZfbQJqPWY&b#nB+ zDzIu`s$*u@z@+$oI~~ICKDtT4lT++cN4yFvHjjOL=~7wJrxL}MbY34z zpSESqHt+F0>Opm;&9>XmnmZu5@3$bi&jlP$>-a0QWBd7lbi^T&tPNgepIf?l!=^(>gVO(TV?Y;8>jPk zx7w(_8ZIpV3a((ihe`B!^nO0CqOaBro!SZNaTcKm)ei>T;agxRGIY|2k}ZMcI`DZ} zB#^j!7fqH^n5HeQ9gwJ&RTq!7;TUH2|GaSG-e_mjQD&was~viKoKax>QUcQM<=Tu!%# zK**rG9k%tb8d%?C>!?xf!b9znZiOD_)z+Bc>;+}Q#vPAMQ{U8An$}UgvLo$P&s)L3 zaz#g}dgAPZqvJjMV_l_Uy3cxb0}|U+=c$lWyPW^!;6q+`roO@x|XuS38U}YMjnIX7|!Evux|4OW4139oO@w zY;hd~c+SYg58a0vvt%1Imx`0DB>80PA{}v>#R8titr(mrIcMAs{Btuzg-s{W3@d7H{Nhf z5LlR9hQ|8TG);eLY-+OId%xjZe|b#G#(TBon2DN2EbiN7@rY-u{rIN3XWqjHI^}ri zqx2g)$6Z8Aj0=ZlyMOP1c}(9)SUMeEzI%7$y(fPP7H4jG67oFqw9%{WHwoS@h-7;i z<6tR*4@jWB5LrmnoOqcN^hK_3G%uK(+(Z8zkT5)BV&q?0$LbJ}dYb)VS)Ar7iwQjD zDxW4#If%hQ0tjb62HGoeJy_2!*uyOt19uO^&;)=fLKWo{Rpg+ztYJ6dH}Aj|Z^|hu z!xa_XBNs*f$KdPd;f;;>{|0VJ`*NCrz5JrtQ1Q_X=>o#b+KKus| C1XCXX literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_revert.png b/res/drawable-hdpi/ic_menu_revert.png new file mode 100644 index 0000000000000000000000000000000000000000..ffc67d913f3d17aaee7efe2010fb2d447a4218fe GIT binary patch literal 2093 zcmZ{ldpr{g8^`C8OIXf?D2|O1oiX>!bqI_lW*6@~^OEhMNCqUWq^u{?i;IZyf;u zL;(&+1U7ha<9_JP0y~A_)bY{pWj&<}W{EP=!Tu6#6(}ee%-F$Dpduto&8raTkga3F z09p#dacZ2%$7)GEkd^%;EyR&~i*c^vTu0*&!U(xMG^<}r;^WTaPbRrPk5vvuH2G}} z?2Qjc2fZ`m5#Eu@70%YtDzi2Dsi{_tr!_odWp?aR_;x-}AWmTG>Uxe~i_Co}ATB%V zD^GwxzuL3&C8%%yqz|Yk1;)C(^u3q0?^FJLuc1&P*^t>YEwgrFOV9U3_OW&$h8e0FE|}scjt<$s(0EYdX*_u@|ShGFZ1E(+o14@ zUfBLdxtfTSWRe9L$GRWw9cr@Z4nFt_g(hnq$KsvIeD0^H$x7pOuv&Sg%hcg{7s8VmI&QEn3N;EcS5atQ1AP(~6%jKPxR_ zN=|H1vL$xHR&z-qls)>JrvhQsz!%I~N#1*EewOY4s=Yk*;2=6VpvNF6N%B@45uD}J zZJmF1c8c`ZthLlD`JQXv3lA|=v4#BOz}9?qje9_+*^h*-)kVJ#pL22rPUT$@b+|N# zP!&Hw3BrRzw|#g|*qz-7NPOO~ged0Bqx4CJL?QF$Nm^|0=LFPk5D0`fN7U76MTF6V zv<%e?o4x^+Ui;;Fv0OdUNbpkp?-@FW?v1g#QdHZX)Hi9kmiTYPl1$liL-B1Kw-DmN z!NN(A;S`>IqV|^z?)99j=c%RvW1UaqgzKF$LzebZ%J2x5nH|!ewo!U}iD5zzULD(R zFd6*ADlH=upR<|Dd%EHs(5=d#K+I%Tj@DQrk+hA?j;YuV}!l5Nm6 zwwMtAb+owL{a2To>~&_Z3+hE}9d2vu^koqd_|B7>@wrI0`ts%opgWy;z)IoV|J|rJ z>GO>Zw&T8lUPA7Vi35b-7llwq#IbRth^&ejHO8Ik3f;y<+CGhb~i zUlv+#Z;NxGwnnleycGIy5X=&`S+Bh{ht${HSka?~rmhBdEuTqx-yJ<3Lt3e5l_AUm z8`Y$X54-%<)k}Ok^dFo%zD`|Uqky4P1{+A?@bOchH(X2PycKW9u$M9Qy^}irgH+qd z_m8d=HYXe8ls?o7{<`V$I@}W%-dR(jdIuN{*GTU;*Jck9-(^;8l}%-lQ|kFwL$LPb zr12iKHg&BiB2!+SK?;wN|yOBI{PmQLgO+i&Qs_?+S;u#XjO9C2X}^Ib!8~y zW*glhc?L;b9jQ>pfHH1Myd0s)s0O(;Ha+=LuvxZtsZ&AF(-^d{D0k?tYC44t!)7Ze zF3T-@OWg)2v+^U7^{G4D;X3(ghPbHrJ9A|yNrDGCDT%fyF&8OI{{%w16E2(@Y1p|XQWoJebV+)6a5I0izie^>1jW}QaTGf zfGKyMfmiflo{Xk?O{9%W>GeV`vG}nCuC>NoOCgtPH#9{hU49?U$qCpcqvusvbJxhhi2#NLl7~#KUY+SapJXnClFNMQdL;gXE2#D~p(?FmrYx~1q{z3eIbqh6fPFtY zW2A~!6%x2{f#{RTd)=&(UD-4`c)`#V{s!59GwwoEz#GeZ+7=>SmWCKWO@2w;%WEDo zi*v-`%*01aflN*n+4?~AR@d31gKEyn%S#$EalIsSkn-EMhC^Mre!9}xFW$PdzhSq@ z)^c>8(ZX3)AqzUtYref!5`)fmUOigZ#2_27EOaF{+6 zW(0@9suy18{f{9mJdhYf{{M#QGy;@wIQC;h5RnvvkAmY!u~7h+mX2!-A&HNG|Ak25 zID9lfKb9s2;S+@)#5Fu9CeA+!4?qO+p^8J8KLL-y`v;OEnRrwF5Wv9(g>16&qx=o{ CXx!rf literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_save.png b/res/drawable-hdpi/ic_menu_save.png new file mode 100644 index 0000000000000000000000000000000000000000..62d0b9a00b810234fecb3d45ba97bb1d7eb13726 GIT binary patch literal 2050 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 literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1095584ec21f71cd0afc9e0993aa2209671b590c GIT binary patch literal 1723 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 + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..197963e --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,9 @@ + + + XServer + + Save + Delete + Revert changes + Discard + diff --git a/src/tdm/xserver/ByteQueue.java b/src/tdm/xserver/ByteQueue.java new file mode 100644 index 0000000..bd5433e --- /dev/null +++ b/src/tdm/xserver/ByteQueue.java @@ -0,0 +1,152 @@ +package tdm.xserver; + +import android.util.Log; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +class ByteQueue +{ + ByteBuffer mData; + + ByteQueue() { + mData = ByteBuffer.allocate(1); + } + ByteQueue(int len) { + mData = ByteBuffer.allocate(len); + mData.limit(0); + } + ByteQueue(byte[] src, int pos, int len) { + mData = ByteBuffer.allocate(len); + mData.put(src, pos, len); + mData.position(0); + } + ByteQueue(byte[] src) { + mData = ByteBuffer.allocate(src.length); + mData.put(src); + mData.position(0); + } + + void dump() { + Log.e(XServer.TAG, "ByteQueue.dump: limit=" + mData.limit() + + ", capacity=" + mData.capacity() + ", pos=" + mData.position()); + StringBuffer hexbuf = new StringBuffer(); + int x, y; + for (y = 0; y < mData.limit(); y += 16) { + hexbuf.append(String.format("%04x: ", y)); + for (x = 0; x < 15 && y+x < mData.limit(); ++x) { + if (x > 0) { + hexbuf.append(" "); + } + hexbuf.append(String.format("%02x", mData.get(y+x))); + } + Log.e(XServer.TAG, " " + hexbuf); + hexbuf = new StringBuffer(); + } + } + + void clear() { mData.position(0); mData.limit(0); } + void resize(int len) { + if (len == 0) { + Log.e("X", "ByteQueue.resize: new len is zero"); + len = 1; + } + if (mData.capacity() < len) { + int newlen = mData.capacity(); + if (newlen == 0) { + Log.e("X", "ByteQueue.resize: capacity is zero"); + Thread.currentThread().dumpStack(); + newlen = 1; + } + while (newlen < len) { newlen *= 2; } //XXX: better way? overflow? + ByteBuffer newbuf = ByteBuffer.allocate(newlen); + newbuf.order(mData.order()); + System.arraycopy(mData.array(), 0, newbuf.array(), 0, mData.limit()); + newbuf.position(mData.position()); + mData = newbuf; + } + mData.limit(len); + } + + ByteOrder endian() { return mData.order(); } + void endian(ByteOrder val) { mData.order(val); } + + int pos() { return mData.position(); } + void pos(int val) { mData.position(val); } + + int length() { return mData.limit(); } + int remain() { return length() - pos(); } + + void compact() { + mData.compact(); + mData.limit(mData.position()); + } + + byte[] getBytes() { + byte[] b = new byte[mData.position()]; + System.arraycopy(mData.array(), 0, b, 0, mData.position()); + return b; + } + + void deqSkip(int n) { + int pos = mData.position(); + mData.position(pos+n); + } + void deqAlign(int n) { + int pos = mData.position(); + mData.position(MathX.roundup(pos, n)); + } + + byte deqByte() { return mData.get(); } + short deqShort() { return mData.getShort(); } + int deqInt() { return mData.getInt(); } + + byte[] deqArray(int len) { + byte[] val = new byte[len]; + mData.get(val); + return val; + } + ByteQueue deqData(int len) { + byte[] val = deqArray(len); + ByteQueue data = new ByteQueue(val); + data.mData.order(mData.order()); + return data; + } + String deqString(int len) { + byte[] val = deqArray(len); + return new String(val); + } + + void enqSkip(int n) { + int pos = mData.position(); + int newpos = pos+n; + resize(newpos); + mData.position(newpos); + } + void enqAlign(int n) { + int pos = mData.position(); + int newpos = MathX.roundup(pos, n); + resize(newpos); + mData.position(newpos); + } + + void enqByte(byte val) { resize(mData.position() + 1); mData.put(val); } + void enqShort(short val) { resize(mData.position() + 2); mData.putShort(val); } + void enqInt(int val) { resize(mData.position() + 4); mData.putInt(val); } + + void enqArray(byte[] val, int pos, int len) { + resize(mData.position() + len); + mData.put(val, pos, len); + } + void enqArray(byte[] val) { + resize(mData.position() + val.length); + mData.put(val); + } + void enqData(ByteQueue val) { + resize(mData.position() + val.pos()); + mData.put(val.mData.array(), 0, val.pos()); + } + void enqString(String val) { + enqArray(val.getBytes()); + } +} diff --git a/src/tdm/xserver/ClientView.java b/src/tdm/xserver/ClientView.java new file mode 100644 index 0000000..64a0cd1 --- /dev/null +++ b/src/tdm/xserver/ClientView.java @@ -0,0 +1,129 @@ +package tdm.xserver; + +import android.util.Log; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; + +import android.view.View; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +class ClientView extends View +{ + X11Window mWindow; + X11Client mClient; + + ClientView(Context ctx, X11Window w) { + super(ctx); + Log.d("X", "ClientView ctor"); + mWindow = w; + } + + void destroy() { + mClient = null; + mWindow = null; + } + + protected void onMeasure(int wms, int hms) { + Log.d("X", "ClientView#"+mWindow.mId+".onMeasure: wms=" + wms + ", hms=" + hms + + ", w=" + mWindow.mRect.w + ", h=" + mWindow.mRect.h); + setMeasuredDimension(mWindow.mRect.w, mWindow.mRect.h); + } + + public void onDraw(Canvas canvas) { + Log.d("X", "ClientView#"+mWindow.mId+".onDraw"); + canvas.drawBitmap(mWindow.mBitmap, 0, 0, null); + } + + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + // Do not handle non-pointer motion (eg. joystick) + return true; //XXX? super.onGenericMotionEvent(event); + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: // 0 + Log.d("X", "MotionEvent: ACTION_DOWN"); + mWindow.onButtonPress(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + case MotionEvent.ACTION_UP: // 1 + Log.d("X", "MotionEvent: ACTION_UP"); + mWindow.onButtonRelease(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + case MotionEvent.ACTION_MOVE: // 2 + Log.d("X", "MotionEvent: ACTION_MOVE"); + mWindow.onMotion(mClient, (int)event.getX(), (int)event.getY()); + break; + // ACTION_CANCEL == 3 + // ACTION_OUTSIDE == 4 + case MotionEvent.ACTION_POINTER_DOWN: // 5 + Log.d("X", "MotionEvent: ACTION_POINTER_DOWN"); + mWindow.onButtonPress(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + case MotionEvent.ACTION_POINTER_UP: // 6 + Log.d("X", "MotionEvent: ACTION_POINTER_UP"); + mWindow.onButtonRelease(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + //XXX: MotionEvent.ACTION_HOVER_MOVE + //XXX: MotionEvent.ACTION_SCROLL + } + return true; + } + + public boolean onTouchEvent(MotionEvent event) { + //XXX: MotienEvent.getSource() is apparently not in FroYo :/ + // E/AndroidRuntime( 1992): java.lang.NoSuchMethodError: android.view.MotionEvent.getSource + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + // Do not handle non-pointer motion (eg. joystick) + return true; //XXX? super.onTouchEvent(event); + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: // 0 + Log.d("X", "TouchEvent: ACTION_DOWN"); + mWindow.onButtonPress(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + case MotionEvent.ACTION_UP: // 1 + Log.d("X", "TouchEvent: ACTION_UP"); + mWindow.onButtonRelease(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + case MotionEvent.ACTION_MOVE: // 2 + Log.d("X", "TouchEvent: ACTION_MOVE"); + mWindow.onMotion(mClient, (int)event.getX(), (int)event.getY()); + break; + // ACTION_CANCEL == 3 + // ACTION_OUTSIDE == 4 + case MotionEvent.ACTION_POINTER_DOWN: // 5 + Log.d("X", "TouchEvent: ACTION_POINTER_DOWN"); + mWindow.onButtonPress(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + case MotionEvent.ACTION_POINTER_UP: // 6 + Log.d("X", "TouchEvent: ACTION_POINTER_UP"); + mWindow.onButtonRelease(mClient, (int)event.getX(), (int)event.getY(), 0); + break; + //XXX: MotionEvent.ACTION_HOVER_MOVE + //XXX: MotionEvent.ACTION_SCROLL + } + return true; + } + + public boolean onKeyDown(int code, KeyEvent event) { + Log.d("X", "ClientView#"+mWindow.mId+".onKeyDown("+code+"): char="+event.getUnicodeChar()); + mWindow.onKeyPress(mClient, code); + mClient.mServer.mKeyboard.onKeyDown(code); + return true; + } + + public boolean onKeyUp(int code, KeyEvent event) { + Log.d("X", "ClientView#"+mWindow.mId+".onKeyUp("+code+")"); + mWindow.onKeyRelease(mClient, code); + mClient.mServer.mKeyboard.onKeyUp(code); + return true; + } + + protected void onFocusChanged(boolean gainFocus, int direction, Rect prev) { + Log.d("X", "ClientView#"+mWindow.mId+".onFocusChanged("+gainFocus+")"); + } +} diff --git a/src/tdm/xserver/FontData.java b/src/tdm/xserver/FontData.java new file mode 100644 index 0000000..b96adaa --- /dev/null +++ b/src/tdm/xserver/FontData.java @@ -0,0 +1,141 @@ +package tdm.xserver; + +import android.util.Log; + +import android.graphics.Bitmap; + +import java.util.ArrayList; + +import java.io.File; + +class FontData +{ + static void globalInit(X11Server server) { + FontDataPCF.globalInit(server); + FontDataNative.globalInit(server); + } + + String mName; + boolean mMetadataLoaded; + boolean mGlyphsLoaded; + + X11CharInfo mMinBounds; + X11CharInfo mMaxBounds; + short mMinCharOrByte2; + short mMaxCharOrByte2; + short mDefaultChar; + byte mDrawDirection; + byte mMinByte1; + byte mMaxByte1; + byte mAllCharsExist; + short mFontAscent; + short mFontDescent; + ArrayList mProperties; + ArrayList mCharInfos; + + ArrayList mImages; + + FontData(String name) { + mName = name; + mMetadataLoaded = false; + mGlyphsLoaded = false; + + mMinBounds = new X11CharInfo(); + mMinBounds.left_side_bearing = 0; + mMinBounds.right_side_bearing = 0; + mMinBounds.character_width = 0; + mMinBounds.ascent = 0; + mMinBounds.descent = 0; + mMinBounds.attributes = 0; + + mMaxBounds = new X11CharInfo(); + mMaxBounds.left_side_bearing = 0; + mMaxBounds.right_side_bearing = 0; + mMaxBounds.character_width = 0; + mMaxBounds.ascent = 0; + mMaxBounds.descent = 0; + mMaxBounds.attributes = 0; + + mMinCharOrByte2 = 0; + mMaxCharOrByte2 = 0; + mDefaultChar = 0; + mDrawDirection = 0; + mMinByte1 = 0; + mMaxByte1 = 0; + mAllCharsExist = 1; + mFontAscent = 0; + mFontDescent = 0; + mProperties = new ArrayList(); + mCharInfos = new ArrayList(); + mImages = new ArrayList(); + } + + void loadMetadata(X11Server server) throws Exception { mMetadataLoaded = true; } + void loadGlyphs(X11Server server) throws Exception { mGlyphsLoaded = true; } + + boolean match(String pattern) { + String[] patv = pattern.split("-"); + String[] v = mName.split("-"); + + if (patv.length != v.length) { + return false; + } + + for (int n = 0; n < v.length; ++n) { + if (!patv[n].equals("*") && !patv[n].equals(v[n])) { + return false; + } + } + return true; + } + + Bitmap getBitmap(int idx) { + return mImages.get(idx); + } + + void enqueueInfo(ByteQueue q) { + q.enqShort(mMinBounds.left_side_bearing); + q.enqShort(mMinBounds.right_side_bearing); + q.enqShort(mMinBounds.character_width); + q.enqShort(mMinBounds.ascent); + q.enqShort(mMinBounds.descent); + q.enqShort(mMinBounds.attributes); + q.enqSkip(4); + q.enqShort(mMaxBounds.left_side_bearing); + q.enqShort(mMaxBounds.right_side_bearing); + q.enqShort(mMaxBounds.character_width); + q.enqShort(mMaxBounds.ascent); + q.enqShort(mMaxBounds.descent); + q.enqShort(mMaxBounds.attributes); + q.enqSkip(4); + q.enqShort(mMinCharOrByte2); + q.enqShort(mMaxCharOrByte2); + q.enqShort(mDefaultChar); + q.enqShort((short)mProperties.size()); + q.enqByte(mDrawDirection); + q.enqByte(mMinByte1); + q.enqByte(mMaxByte1); + q.enqByte(mAllCharsExist); + q.enqShort(mFontAscent); + q.enqShort(mFontDescent); + } + void enqueueProperties(ByteQueue q) { + for (X11FontProp prop : mProperties) { + q.enqInt(prop.name); + q.enqInt(prop.value); + } + } + void enqueueCharInfoCount(ByteQueue q) { + q.enqInt((int)mCharInfos.size()); + } + void enqueueCharInfo(ByteQueue q) { + for (X11CharInfo info : mCharInfos) { + q.enqShort(info.left_side_bearing); + q.enqShort(info.right_side_bearing); + q.enqShort(info.character_width); + q.enqShort(info.ascent); + q.enqShort(info.descent); + q.enqShort(info.attributes); + } + } +} diff --git a/src/tdm/xserver/FontDataNative.java b/src/tdm/xserver/FontDataNative.java new file mode 100644 index 0000000..c9b48e0 --- /dev/null +++ b/src/tdm/xserver/FontDataNative.java @@ -0,0 +1,36 @@ +package tdm.xserver; + +import android.graphics.Typeface; + +import java.io.File; + +class FontDataNative extends FontData +{ + static void globalInit(X11Server server) { + // Built-in Android fonts + server.registerFont(new FontDataNative("sans", Typeface.SANS_SERIF)); + server.registerFont(new FontDataNative("serif", Typeface.SERIF)); + server.registerFont(new FontDataNative("monospace", Typeface.MONOSPACE)); + //server.registerFontAlias("fixed", "monospace"); + + File dir = new File("/system/fonts/"); + for (File f : dir.listFiles()) { + String filename = f.getName(); + if (filename.endsWith(".ttf")) { + String fontname = filename.substring(0, filename.lastIndexOf('.')); + server.registerFont(new FontDataNative(fontname, f)); + } + } + } + + Typeface mTypeFace; + + FontDataNative(String name, Typeface tf) { + super(name); + mTypeFace = tf; + } + FontDataNative(String name, File f) { + super(name); + mTypeFace = Typeface.createFromFile(f); + } +} diff --git a/src/tdm/xserver/FontDataPCF.java b/src/tdm/xserver/FontDataPCF.java new file mode 100644 index 0000000..d1fa2a8 --- /dev/null +++ b/src/tdm/xserver/FontDataPCF.java @@ -0,0 +1,636 @@ +package tdm.xserver; + +import android.content.res.AssetManager; +import android.util.Log; + +import android.graphics.Bitmap; +import android.graphics.Color; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.zip.GZIPInputStream; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +class FontDataPCF extends FontData +{ + static final int PATH_TYPE_FILE = 1; + static final int PATH_TYPE_ASSET = 2; + + static final int PCF_PROPERTIES = (1<<0); + static final int PCF_ACCELERATORS = (1<<1); + static final int PCF_METRICS = (1<<2); + static final int PCF_BITMAPS = (1<<3); + static final int PCF_INK_METRICS = (1<<4); + static final int PCF_BDF_ENCODINGS = (1<<5); + static final int PCF_SWIDTHS = (1<<6); + static final int PCF_GLYPH_NAMES = (1<<7); + static final int PCF_BDF_ACCELERATORS = (1<<8); + + static final int PCF_DEFAULT_FORMAT = 0x00000000; + static final int PCF_INKBOUNDS = 0x00000200; + static final int PCF_ACCEL_W_INKBOUNDS = 0x00000100; + static final int PCF_COMPRESSED_METRICS = 0x00000100; + + static final int PCF_GLYPH_PAD_MASK = (3<<0); // See the bitmap table for explanation + static final int PCF_BYTE_MASK = (1<<2); // If set then Most Sig Byte First + static final int PCF_BIT_MASK = (1<<3); // If set then Most Sig Bit First + static final int PCF_SCAN_UNIT_MASK = (3<<4); // See the bitmap table for explanation + + class pcf_toc_entry + { + int type; + int format; + int size; + int offset; + } + class pcf_toc_order_by_offset implements Comparator + { + public int compare(Object o1, Object o2) { + pcf_toc_entry e1 = (pcf_toc_entry)o1; + pcf_toc_entry e2 = (pcf_toc_entry)o2; + return e1.offset - e2.offset; + } + } + + class pcf_property + { + int name_offset; + byte is_string; + int value; + } + + class pcf_metrics + { + short left_side_bearing; + short right_side_bearing; + short character_width; + short character_ascent; + short character_descent; + short character_attributes; + } + + class pcf_accelerator + { + byte no_overlap; + byte constant_metrics; + byte terminal_font; + byte constant_width; + byte ink_inside; + byte ink_metrics; + byte draw_direction; + byte padding; + int font_ascent; + int font_descent; + int max_overlap; + pcf_metrics min_bounds; + pcf_metrics max_bounds; + pcf_metrics ink_min_bounds; + pcf_metrics ink_max_bounds; + + pcf_accelerator() { + min_bounds = new pcf_metrics(); + max_bounds = new pcf_metrics(); + ink_min_bounds = new pcf_metrics(); + ink_max_bounds = new pcf_metrics(); + } + } + + class pcf_encoding + { + short min_char_or_byte2; + short max_char_or_byte2; + short min_byte1; + short max_byte1; + short default_char; + short[] glyph_indices; + } + + static void globalInit(X11Server server) { + BufferedReader br; + String s; + try { + AssetManager am = server.mContext.getAssets(); + + br = new BufferedReader(new InputStreamReader(am.open("fonts/fonts.dir"))); + br.readLine(); // Skip first line + while ((s = br.readLine()) != null) { + String[] fields = s.split(" ", 2); + String pathname = "fonts/" + fields[0]; + FontDataPCF pcf = new FontDataPCF(fields[1], PATH_TYPE_ASSET, pathname); + server.registerFont(pcf); + } + + br = new BufferedReader(new InputStreamReader(am.open("fonts/fonts.alias"))); + while ((s = br.readLine()) != null) { + if (s.length() == 0 || s.charAt(0) == '!') { + continue; + } + String[] fields = s.split("[ \t]{1,}", 2); + server.registerFontAlias(fields[0].toLowerCase(), fields[1].toLowerCase()); + } + } + catch (Exception e) { + Log.e("X", "Failed to init PCF fonts from assets"); + } + + try { + br = new BufferedReader(new InputStreamReader(new FileInputStream("/data/local/fonts/fonts.dir"))); + br.readLine(); // Skip first line + while ((s = br.readLine()) != null) { + if (s.length() == 0 || s.charAt(0) == '!') { + continue; + } + String[] fields = s.split(" ", 2); + if (fields.length != 2) { + Log.e("X", "bad line in fonts.dir: fields=" + fields.length + ", line=" + s); + continue; + } + String pathname = "/data/local/fonts/" + fields[0]; + FontDataPCF pcf = new FontDataPCF(fields[1], PATH_TYPE_FILE, pathname); + server.registerFont(pcf); + } + + br = new BufferedReader(new InputStreamReader(new FileInputStream("/data/local/fonts/fonts.alias"))); + while ((s = br.readLine()) != null) { + if (s.length() == 0 || s.charAt(0) == '!') { + continue; + } + String[] fields = s.split("[ \t]{1,}", 2); + if (fields.length != 2) { + Log.e("X", "bad line in fonts.alias: fields=" + fields.length + ", line=" + s); + continue; + } + server.registerFontAlias(fields[0].toLowerCase(), fields[1].toLowerCase()); + } + } + catch (Exception e) { + Log.e("X", "Failed to init PCF fonts from dir"); + e.printStackTrace(); + } + } + + int mPathtype; // FILE or ASSET + String mPathname; + + ArrayList mGlyphInfos; + + FontDataPCF(String name, int pathtype, String pathname) { + super(name); + mPathtype = pathtype; + mPathname = pathname; + mGlyphInfos = new ArrayList(); + } + + private void readPropertyTable(X11Server server, int fmt, ByteBuffer b) { + b.order(ByteOrder.LITTLE_ENDIAN); + int format = b.getInt(); + b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + + int nprops = b.getInt(); + pcf_property[] props = new pcf_property[nprops]; + + int n; + for (n = 0; n < nprops; ++n) { + pcf_property prop = new pcf_property(); + prop.name_offset = b.getInt(); + prop.is_string = b.get(); + prop.value = b.getInt(); + props[n] = prop; + } + b.position(MathX.roundup(b.position(), 4)); + + int string_size = b.getInt(); + + byte[] string_table = new byte[string_size]; + b.get(string_table); + + mProperties = new ArrayList(nprops); + for (n = 0; n < nprops; ++n) { + String s = new String(string_table, props[n].name_offset); + X11FontProp prop = new X11FontProp(); + prop.name = server.doInternAtom(s); + if (props[n].is_string != 0) { + String val = new String(string_table, props[n].value); + prop.value = server.doInternAtom(val); + } + else { + prop.value = props[n].value; + } + mProperties.add(n, prop); + } + } + + private void readAcceleratorTable(int fmt, ByteBuffer b) { + b.order(ByteOrder.LITTLE_ENDIAN); + int format = b.getInt(); + b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + + pcf_accelerator a = new pcf_accelerator(); + + a.no_overlap = b.get(); + a.constant_metrics = b.get(); + a.terminal_font = b.get(); + a.constant_width = b.get(); + a.ink_inside = b.get(); + a.ink_metrics = b.get(); + a.draw_direction = b.get(); + a.padding = b.get(); + a.font_ascent = b.getInt(); + a.font_descent = b.getInt(); + a.max_overlap = b.getInt(); + + a.min_bounds.left_side_bearing = b.getShort(); + a.min_bounds.right_side_bearing = b.getShort(); + a.min_bounds.character_width = b.getShort(); + a.min_bounds.character_ascent = b.getShort(); + a.min_bounds.character_descent = b.getShort(); + a.min_bounds.character_attributes = b.getShort(); + + a.max_bounds.left_side_bearing = b.getShort(); + a.max_bounds.right_side_bearing = b.getShort(); + a.max_bounds.character_width = b.getShort(); + a.max_bounds.character_ascent = b.getShort(); + a.max_bounds.character_descent = b.getShort(); + a.max_bounds.character_attributes = b.getShort(); + + if ((fmt & PCF_ACCEL_W_INKBOUNDS) != 0) { + a.ink_min_bounds.left_side_bearing = b.getShort(); + a.ink_min_bounds.right_side_bearing = b.getShort(); + a.ink_min_bounds.character_width = b.getShort(); + a.ink_min_bounds.character_ascent = b.getShort(); + a.ink_min_bounds.character_descent = b.getShort(); + a.ink_min_bounds.character_attributes = b.getShort(); + + a.ink_max_bounds.left_side_bearing = b.getShort(); + a.ink_max_bounds.right_side_bearing = b.getShort(); + a.ink_max_bounds.character_width = b.getShort(); + a.ink_max_bounds.character_ascent = b.getShort(); + a.ink_max_bounds.character_descent = b.getShort(); + a.ink_max_bounds.character_attributes = b.getShort(); + } + else { + a.ink_min_bounds = a.min_bounds; + a.ink_max_bounds = a.max_bounds; + } + + mMinBounds.left_side_bearing = a.min_bounds.left_side_bearing; + mMinBounds.right_side_bearing = a.min_bounds.right_side_bearing; + mMinBounds.character_width = a.min_bounds.character_width; + mMinBounds.ascent = a.min_bounds.character_ascent; + mMinBounds.descent = a.min_bounds.character_descent; + mMinBounds.attributes = a.min_bounds.character_attributes; + + mMaxBounds.left_side_bearing = a.max_bounds.left_side_bearing; + mMaxBounds.right_side_bearing = a.max_bounds.right_side_bearing; + mMaxBounds.character_width = a.max_bounds.character_width; + mMaxBounds.ascent = a.max_bounds.character_ascent; + mMaxBounds.descent = a.max_bounds.character_descent; + mMaxBounds.attributes = a.max_bounds.character_attributes; + + mDrawDirection = a.draw_direction; + mAllCharsExist = 0 /* false */; //XXX? + mFontAscent = (short)a.font_ascent; + mFontDescent = (short)a.font_descent; + } + + short decompress_metric(byte b) { + if (b < 0) { + return (short)(b & 0x7f); + } + return (short)(b - 0x80); + } + + private void readMetricsTable(int fmt, ByteBuffer b, ArrayList infos) { + b.order(ByteOrder.LITTLE_ENDIAN); + int format = b.getInt(); + b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + + if ((fmt & PCF_COMPRESSED_METRICS) != 0) { + short count = b.getShort(); + short n; + for (n = 0; n < count; ++n) { + X11CharInfo info = new X11CharInfo(); + info.left_side_bearing = decompress_metric(b.get()); + info.right_side_bearing = decompress_metric(b.get()); + info.character_width = decompress_metric(b.get()); + info.ascent = decompress_metric(b.get()); + info.descent = decompress_metric(b.get()); + info.attributes = 0; + infos.add(n, info); + } + } + else { + int count = b.getInt(); + int n; + for (n = 0; n < count; ++n) { + X11CharInfo info = new X11CharInfo(); + info.left_side_bearing = b.getShort(); + info.right_side_bearing = b.getShort(); + info.character_width = b.getShort(); + info.ascent = b.getShort(); + info.descent = b.getShort(); + info.attributes = b.getShort(); + infos.add(n, info); + } + } + } + + private void showBitmap(char ch) { + Bitmap bmp = mImages.get((int)ch); + Log.d("X", "Show bitmap for <"+ch+">: w="+bmp.getWidth()+", h="+bmp.getHeight()+" ..."); + Log.d("X", "-----------"); + for (int y = 0; y < bmp.getHeight(); ++y) { + StringBuffer buf = new StringBuffer(); + for (int x = 0; x < bmp.getWidth(); ++x) { + if (bmp.getPixel(x, y) == Color.BLACK) { + buf.append("."); + } + else { + buf.append("X"); + } + } + Log.d("X", " " + buf); + } + Log.d("X", "-----------"); + } + + private void readBitmapTable(int fmt, ByteBuffer b) { + b.order(ByteOrder.LITTLE_ENDIAN); + int format = b.getInt(); + b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + + int glyph_count = b.getInt(); + int n; + + int[] offsets = new int[glyph_count]; + for (n = 0; n < glyph_count; ++n) { + offsets[n] = b.getInt(); + } + + int[] bitmap_sizes = new int[4]; + bitmap_sizes[0] = b.getInt(); + bitmap_sizes[1] = b.getInt(); + bitmap_sizes[2] = b.getInt(); + bitmap_sizes[3] = b.getInt(); + + int bitmap_data_len = bitmap_sizes[format&3]; + ByteBuffer bitmap_data = b.slice(); + bitmap_data.limit(bitmap_data_len); + + int bitmap_byte_order = (format&4) >> 2; // 1=LSByteFirst, 0=MSByteFirst + int bitmap_bit_order = (format&8) >> 3; // 1=LSBitFirst, 0=MSBitFirst + + mImages = new ArrayList(glyph_count); + + int row_pad_bytes = (1<<(format&3)); + int elem_bytes = (1<<((format>>4)&3)); + int elem_bits = elem_bytes*8; + +Log.d("X", "readBitmapTable: name="+mName+", glyph_count="+glyph_count+", bitmap_data_len="+bitmap_data_len); + + for (n = 0; n < glyph_count; ++n) { + X11CharInfo info = mGlyphInfos.get(n); + //XXX: should width be rsb-lsb or width? + short w = info.character_width; + short h = (short)(info.ascent + info.descent); + if (w == 0 || h == 0) { + mImages.add(n, null); + continue; + } + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + + int elem_per_row = (w+elem_bits-1)/elem_bits; + int bytes_per_row = ((elem_per_row*elem_bytes+row_pad_bytes-1)/row_pad_bytes)*row_pad_bytes; + + short row, col; + int row_off, col_off, idx; + int elem; + int nbits, bit; + for (row = 0; row < h; ++row) { + row_off = row * bytes_per_row; + for (col = 0; col < w; ++col) { + int elem_off = (col/elem_bits)*elem_bytes; + int elem_byte_off; + if (bitmap_byte_order == 1 /*LSByteFirst*/) { + elem_byte_off = (col%elem_bits)/8; + } + else { + elem_byte_off = elem_bytes-1 - (col%elem_bits)/8; + } + int elem_bit_off; + if (bitmap_bit_order == 1 /*LSBitFirst*/) { + elem_bit_off = 8-1 - (col%8); + } + else { + elem_bit_off = (col%8); + } + int valoff = offsets[n] + row_off + elem_off + elem_byte_off; + byte val = bitmap_data.get(offsets[n] + row_off + elem_off + elem_byte_off); + int pixel = (val >> elem_bit_off) & 1; + int color = (pixel != 0 ? Color.WHITE : Color.BLACK); + bmp.setPixel(col, row, color); + } + } + mImages.add(n, bmp); + } + } + + private void readEncodingTable(int fmt, ByteBuffer b) { + b.order(ByteOrder.LITTLE_ENDIAN); + int format = b.getInt(); + b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + + mMinCharOrByte2 = b.getShort(); + mMaxCharOrByte2 = b.getShort(); + mMinByte1 = (byte)b.getShort(); + mMaxByte1 = (byte)b.getShort(); + mDefaultChar = b.getShort(); + + //XXX: this fails on cu-pua12.pcf.gz + int num_indices = (mMaxCharOrByte2 - + mMinCharOrByte2 + 1) * + (mMaxByte1 - + mMinByte1 + 1); + short[] glyph_indices = new short[num_indices]; + int n; + for (n = 0; n < num_indices; ++n) { + glyph_indices[n] = b.getShort(); + } + } + + private void readScalableWidthsTable(int fmt, ByteBuffer b) { + //XXX: Skip this data + } + + private void readGlyphNamesTable(int fmt, ByteBuffer b) { + //XXX: Skip this data + } + + void loadMetadata(X11Server server) throws Exception { + if (mMetadataLoaded) { + return; + } + InputStream is = null; + if (mPathtype == PATH_TYPE_ASSET) { + AssetManager am = server.mContext.getAssets(); + is = am.open(mPathname); + } + else { + is = new FileInputStream(mPathname); + } + //XXX: should check for gzip signature, but what about rewind? + if (mPathname.endsWith(".gz")) { + is = new GZIPInputStream(is); + } + + long pos = 0; //XXX: seek() sure would be nice + ByteBuffer b; + + b = ByteBuffer.allocate(4); + is.read(b.array(), 0, 4); pos += 4; + byte[] sig = b.array(); + if (sig[0] != 1 || sig[1] != 'f' || sig[2] != 'c' || sig[3] != 'p') { + throw new Exception("Bad PCF header"); + } + + b = ByteBuffer.allocate(4); + b.order(ByteOrder.LITTLE_ENDIAN); + is.read(b.array(), 0, 4); pos += 4; + int table_count = b.getInt(); + + int n; + pcf_toc_entry[] toc = new pcf_toc_entry[table_count]; + for (n = 0; n < table_count; ++n) { + b = ByteBuffer.allocate(16); + b.order(ByteOrder.LITTLE_ENDIAN); + is.read(b.array(), 0, 16); pos += 16; + pcf_toc_entry ent = new pcf_toc_entry(); + ent.type = b.getInt(); + ent.format = b.getInt(); + ent.size = b.getInt(); + ent.offset = b.getInt(); + toc[n] = ent; + } + Arrays.sort(toc, new pcf_toc_order_by_offset()); + + for (pcf_toc_entry e : toc) { + if (pos != e.offset) { + is.skip(e.offset - pos); + pos = e.offset; + } + + b = ByteBuffer.allocate(e.size); + int off = 0; + while (off < e.size) { + int nread = is.read(b.array(), off, e.size-off); + if (nread <= 0) { + Log.w("X", "FontDataPCF.loadMetadata: short read in " + mPathname); + break; + } + off += nread; + pos += nread; + } + + switch (e.type) { + case PCF_PROPERTIES: readPropertyTable(server, e.format, b); break; + case PCF_ACCELERATORS: readAcceleratorTable(e.format, b); break; + case PCF_METRICS: readMetricsTable(e.format, b, mGlyphInfos); break; + case PCF_BITMAPS: /* Ignore */ break; + case PCF_INK_METRICS: readMetricsTable(e.format, b, mCharInfos); break; + case PCF_BDF_ENCODINGS: readEncodingTable(e.format, b); break; + case PCF_SWIDTHS: readScalableWidthsTable(e.format, b); break; + case PCF_GLYPH_NAMES: readGlyphNamesTable(e.format, b); break; + case PCF_BDF_ACCELERATORS: readAcceleratorTable(e.format, b); break; + default: throw new Exception("Bad PCF toc"); + } + } + + mMetadataLoaded = true; + } + + void loadGlyphs(X11Server server) throws Exception { + if (mGlyphsLoaded) { + return; + } + Log.d("X", "FontDataPCF.loadGlyphs"); + + InputStream is = null; + if (mPathtype == PATH_TYPE_ASSET) { + AssetManager am = server.mContext.getAssets(); + is = am.open(mPathname); + } + else { + is = new FileInputStream(mPathname); + } + //XXX: should check for gzip signature, but what about rewind? + if (mPathname.endsWith(".gz")) { + is = new GZIPInputStream(is); + } + + long pos = 0; //XXX: seek() sure would be nice + ByteBuffer b; + + b = ByteBuffer.allocate(4); + is.read(b.array(), 0, 4); pos += 4; + byte[] sig = b.array(); + if (sig[0] != 1 || sig[1] != 'f' || sig[2] != 'c' || sig[3] != 'p') { + throw new Exception("Bad PCF header"); + } + + b = ByteBuffer.allocate(4); + b.order(ByteOrder.LITTLE_ENDIAN); + is.read(b.array(), 0, 4); pos += 4; + int table_count = b.getInt(); + + int n; + pcf_toc_entry[] toc = new pcf_toc_entry[table_count]; + for (n = 0; n < table_count; ++n) { + b = ByteBuffer.allocate(16); + b.order(ByteOrder.LITTLE_ENDIAN); + is.read(b.array(), 0, 16); pos += 16; + pcf_toc_entry ent = new pcf_toc_entry(); + ent.type = b.getInt(); + ent.format = b.getInt(); + ent.size = b.getInt(); + ent.offset = b.getInt(); + toc[n] = ent; + } + Arrays.sort(toc, new pcf_toc_order_by_offset()); + + for (pcf_toc_entry e : toc) { + if (pos != e.offset) { + is.skip(e.offset - pos); + pos = e.offset; + } + + b = ByteBuffer.allocate(e.size); + int off = 0; + while (off < e.size) { + int nread = is.read(b.array(), off, e.size-off); + if (nread <= 0) { + Log.w("X", "FontDataPCF.loadGlyphs: short read in " + mPathname); + break; + } + off += nread; + pos += nread; + } + + switch (e.type) { + case PCF_BITMAPS: readBitmapTable(e.format, b); break; + default: /* Ignore */ break; + } + } + + mGlyphsLoaded = true; + } +} diff --git a/src/tdm/xserver/MathX.java b/src/tdm/xserver/MathX.java new file mode 100644 index 0000000..200cc47 --- /dev/null +++ b/src/tdm/xserver/MathX.java @@ -0,0 +1,18 @@ +package tdm.xserver; + +class MathX +{ + public static short divceil(short val, int n) { + return (short)((val+n-1)/n); + } + public static int divceil(int val, int n) { + return (val+n-1)/n; + } + + public static short roundup(short val, int n) { + return (short)(divceil(val,n)*n); + } + public static int roundup(int val, int n) { + return divceil(val,n)*n; + } +} diff --git a/src/tdm/xserver/UIHandler.java b/src/tdm/xserver/UIHandler.java new file mode 100644 index 0000000..e04b974 --- /dev/null +++ b/src/tdm/xserver/UIHandler.java @@ -0,0 +1,104 @@ +package tdm.xserver; + +import android.util.Log; + +import android.content.Context; + +import android.os.Handler; +import android.os.Message; + +import android.graphics.drawable.BitmapDrawable; + +import android.view.View; +import android.view.ViewGroup; +import android.view.Gravity; + +import android.widget.RelativeLayout; + +class UIHandler extends Handler +{ + public static final int MSG_VIEW_CREATE_ROOT = 0x1001; + public static final int MSG_VIEW_CREATE = 0x1002; + public static final int MSG_VIEW_REMOVE = 0x1003; + public static final int MSG_VIEW_CONFIGURE = 0x1004; + public static final int MSG_VIEW_SET_VISIBLE = 0x1010; + public static final int MSG_VIEW_SET_BACKGROUND_COLOR = 0x1011; + public static final int MSG_VIEW_SET_BACKGROUND_BITMAP = 0x1012; + public static final int MSG_VIEW_INVALIDATE = 0x1020; + + Context mContext; + ViewGroup mViewGroup; + + UIHandler(Context ctx, ViewGroup vg) { + mContext = ctx; + mViewGroup = vg; + } + + public void handleMessage(Message msg) { + X11Window w; + RelativeLayout.LayoutParams lp; + switch (msg.what) { + case MSG_VIEW_CREATE_ROOT: + w = (X11Window)msg.obj; + w.mView = new ClientView(mContext, w); + lp = new RelativeLayout.LayoutParams(w.mRect.x, w.mRect.y); + mViewGroup.addView(w.mView, lp); + w.mView.setFocusable(true); + w.mView.setFocusableInTouchMode(true); + w.mView.setVisibility(View.VISIBLE); + Log.d("X", "UI: w="+w.mId+": Attached ClientView to root window"); + break; + case MSG_VIEW_CREATE: + w = (X11Window)msg.obj; + w.mView = new ClientView(mContext, w); + lp = new RelativeLayout.LayoutParams(w.mRect.w, w.mRect.h); + lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, -1); + lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, -1); + lp.setMargins(w.mRect.x, w.mRect.y, 0, 0); + mViewGroup.addView(w.mView, lp); + w.mView.setFocusable(true); + w.mView.setFocusableInTouchMode(true); + w.mView.setVisibility(View.INVISIBLE); + Log.d("X", "UI: w="+w.mId+": Attached ClientView to window at x="+w.mRect.x+", y=" + w.mRect.y); + break; + case MSG_VIEW_REMOVE: + w = (X11Window)msg.obj; + w.mRealized = false; + mViewGroup.removeView(w.mView); + w.mView.destroy(); + w.mView = null; + break; + case MSG_VIEW_CONFIGURE: + w = (X11Window)msg.obj; + lp = new RelativeLayout.LayoutParams(w.mRect.w, w.mRect.h); + lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, -1); + lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, -1); + lp.setMargins(w.mRect.x, w.mRect.y, 0, 0); + //XXX: better way? + mViewGroup.removeView(w.mView); + mViewGroup.addView(w.mView, lp); + break; + case MSG_VIEW_SET_VISIBLE: + w = (X11Window)msg.obj; + w.mView.setVisibility(View.VISIBLE); + w.mRealized = true; + w.mView.requestFocus(); + Log.d("X", "UI: w="+w.mId+": Set window visible"); + break; + case MSG_VIEW_SET_BACKGROUND_COLOR: + w = (X11Window)msg.obj; + w.mView.setBackgroundColor(w.mBgPixel); + break; + case MSG_VIEW_SET_BACKGROUND_BITMAP: + w = (X11Window)msg.obj; + w.mView.setBackgroundDrawable( + new BitmapDrawable(mContext.getResources(), + w.mBgPixmap.mBitmap)); + break; + case MSG_VIEW_INVALIDATE: + w = (X11Window)msg.obj; + w.mView.invalidate(); + break; + } + } +} diff --git a/src/tdm/xserver/X11Atom.java b/src/tdm/xserver/X11Atom.java new file mode 100644 index 0000000..60b7ffa --- /dev/null +++ b/src/tdm/xserver/X11Atom.java @@ -0,0 +1,99 @@ +package tdm.xserver; + +class X11Atom +{ + static final int NONE = 0; + + static final int NR_PREDEFINED = 68; + static final String predefined_names[] = { + "(none)", // Invalid, placeholder + "PRIMARY", // 1 + "SECONDARY", + "ARC", + "ATOM", + "BITMAP", + "CARDINAL", + "COLORMAP", + "CURSOR", + "CUT_BUFFER0", + "CUT_BUFFER1", // 10 + "CUT_BUFFER2", + "CUT_BUFFER3", + "CUT_BUFFER4", + "CUT_BUFFER5", + "CUT_BUFFER6", + "CUT_BUFFER7", + "DRAWABLE", + "FONT", + "INTEGER", + "PIXMAP", // 20 + "POINT", + "RECTANGLE", + "RESOURCE_MANAGER", + "RGB_COLOR_MAP", + "RGB_BEST_MAP", + "RGB_BLUE_MAP", + "RGB_DEFAULT_MAP", + "RGB_GRAY_MAP", + "RGB_GREEN_MAP", + "RGB_RED_MAP", // 30 + "STRING", + "VISUALID", + "WINDOW", + "WM_COMMAND", + "WM_HINTS", + "WM_CLIENT_MACHINE", + "WM_ICON_NAME", + "WM_ICON_SIZE", + "WM_NAME", + "WM_NORMAL_HINTS", // 40 + "WM_SIZE_HINTS", + "WM_ZOOM_HINTS", + "MIN_SPACE", + "NORM_SPACE", + "MAX_SPACE", + "END_SPACE", + "SUPERSCRIPT_X", + "SUPERSCRIPT_Y", + "SUBSCRIPT_X", + "SUBSCRIPT_Y", // 50 + "UNDERLINE_POSITION", + "UNDERLINE_THICKNESS", + "STRIKEOUT_ASCENT", + "STRIKEOUT_DESCENT", + "ITALIC_ANGLE", + "X_HEIGHT", + "QUAD_WIDTH", + "WEIGHT", + "POINT_SIZE", + "RESOLUTION", // 60 + "COPYRIGHT", + "NOTICE", + "FONT_NAME", + "FAMILY_NAME", + "FULL_NAME", + "CAP_HEIGHT", + "WM_CLASS", + "WM_TRANSIENT_FOR" + }; + + static final boolean predefined(int id) { + return (id >= 1 && id < NR_PREDEFINED); + } + + static void globalInit(X11Server server) { + int i; + for (i = 1; i <= NR_PREDEFINED; ++i) { + server.doInternAtom(i, predefined_names[i]); + } + server.mLastAtomId = NR_PREDEFINED; + } + + int mId; + String mName; + + X11Atom(int id, String name) { + mId = id; + mName = name; + } +} diff --git a/src/tdm/xserver/X11CharInfo.java b/src/tdm/xserver/X11CharInfo.java new file mode 100644 index 0000000..d08cda4 --- /dev/null +++ b/src/tdm/xserver/X11CharInfo.java @@ -0,0 +1,11 @@ +package tdm.xserver; + +class X11CharInfo +{ + short left_side_bearing; + short right_side_bearing; + short character_width; + short ascent; + short descent; + short attributes; +} diff --git a/src/tdm/xserver/X11Client.java b/src/tdm/xserver/X11Client.java new file mode 100644 index 0000000..059d08f --- /dev/null +++ b/src/tdm/xserver/X11Client.java @@ -0,0 +1,320 @@ +package tdm.xserver; + +import android.util.Log; + +import android.os.Message; + +import java.io.IOException; +import java.net.Socket; + +import java.nio.ByteBuffer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +class X11Client extends Thread implements X11ProtocolHandler +{ + // NR_BITS_RESOURCES + NR_BITS_CLIENTS must be 29 + static final int NR_BITS_RESOURCES = 22; + + static final int NR_BITS_CLIENTS = (29-NR_BITS_RESOURCES); + static final int CLIENT_ID_SHIFT = NR_BITS_RESOURCES; + static final int MAX_CLIENTS = (1< mResources; + + X11Client(X11Server server) { + mServer = server; + mResources = new HashMap(); + } + X11Client(X11Server server, int id, Socket sock) throws Exception { + Log.e(XServer.TAG, "new client from " + sock.getRemoteSocketAddress().toString()); + mServer = server; + mId = id; + mProt = new X11Protocol(this, sock); + mSeqNo = 0; + mResources = new HashMap(); + } + + public void run() { + while (mProt != null) { + try { + mProt.read(); + } + catch (Exception e) { + Log.e(XServer.TAG, "X11Client: terminating on exception: " + e.toString()); + e.printStackTrace(); + close(); + } + } + + //XXX: release grabs + //XXX: delete selection + //XXX: free resources + // ... + for (X11Resource r : mResources.values()) { + r.destroy(); + } + + mServer.clientClosed(this); + mServer = null; + } + + private final void close() { + mProt = null; + } + + void addResource(X11Resource r) throws X11Error { + if (mResources.containsKey(r.mId)) { + throw new X11Error(X11Error.IDCHOICE, r.mId); + } + mResources.put(r.mId, r); + } + + void delResource(int id) { + mResources.remove(id); + } + + X11Resource getResource(int id) throws X11Error { + X11Client c = mServer.mClients[id >> CLIENT_ID_SHIFT]; + X11Resource r = c.mResources.get(id); + if (r == null) { + throw new X11Error(X11Error.MATCH, id); + } + return r; + } + + X11Resource getResource(int id, int type) throws X11Error { + X11Resource r = getResource(id); + if (r.mType != type) { + throw new X11Error(X11Error.MATCH, id); + } + return r; + } + + X11Pixmap getPixmap(int id) throws X11Error { + return (X11Pixmap)getResource(id, X11Resource.PIXMAP); + } + + X11Window getWindow(int id) throws X11Error { + return (X11Window)getResource(id, X11Resource.WINDOW); + } + + X11Colormap getColormap(int id) throws X11Error { + return (X11Colormap)getResource(id, X11Resource.COLORMAP); + } + + X11Cursor getCursor(int id) throws X11Error { + return (X11Cursor)getResource(id, X11Resource.CURSOR); + } + + X11Font getFont(int id) throws X11Error { + return (X11Font)getResource(id, X11Resource.FONT); + } + + X11GContext getGContext(int id) throws X11Error { + return (X11GContext)getResource(id, X11Resource.GCONTEXT); + } + + X11Drawable getDrawable(int id) throws X11Error { + X11Resource r = getResource(id); + if (r.mType != X11Resource.PIXMAP && r.mType != X11Resource.WINDOW) { + throw new X11Error(X11Error.MATCH, id); + } + return (X11Drawable)r; + } + + X11Fontable getFontable(int id) throws X11Error { + X11Resource r = getResource(id); + if (r.mType != X11Resource.GCONTEXT && r.mType != X11Resource.FONT) { + throw new X11Error(X11Error.MATCH, id); + } + return (X11Fontable)r; + } + + void send(X11ReplyMessage msg) { + msg.seqno(mSeqNo); + Log.d(XServer.TAG, "Send reply: seqno=" + mSeqNo); + try { + mProt.send(msg); + } + catch (IOException e) { + close(); + } + } + void send(X11EventMessage msg) { + msg.seqno(mSeqNo); + Log.d(XServer.TAG, "Send event: seqno=" + mSeqNo + ", name=" + msg.name()); + try { + mProt.send(msg); + } + catch (IOException e) { + close(); + } + } + void send(X11ErrorMessage msg) { + msg.seqno(mSeqNo); + Log.d(XServer.TAG, "Send error: seqno=" + mSeqNo + ", name=" + msg.name()); + try { + mProt.send(msg); + } + catch (IOException e) { + close(); + } + } + + X11Visual getVisual(int id) throws X11Error { + return mServer.getVisual(id); + } + + public void onMessage(X11SetupRequest msg) { + //XXX: always allow for now + Log.e(XServer.TAG, "Got setup request"); + + String vendor = "My Vendor"; + + ArrayList pixmap_formats = mServer.getPixmapFormats(); + + X11SetupResponse r = new X11SetupResponse(msg); + + r.mSuccess = 0x01; // Success + r.mProtoMajor = 11; + r.mProtoMinor = 0; + + r.mData.enqInt((int)0); // release-number + r.mData.enqInt(mId << CLIENT_ID_SHIFT); // resource-id-base + r.mData.enqInt((1 << CLIENT_ID_SHIFT) - 1); // resource-id-mask + r.mData.enqInt((int)0); // motion-buffer-size + r.mData.enqShort((short)vendor.length()); + r.mData.enqShort((short)0xffff); // maximum-request-length + r.mData.enqByte((byte)1); // number of SCREENs in roots + r.mData.enqByte((byte)pixmap_formats.size()); // number of FORMATs in pixmap-formats + r.mData.enqByte((byte)0); // image-byte-order = LSBFirst + r.mData.enqByte((byte)0); // bitmap-format-bit-order = LeastSignificant + r.mData.enqByte((byte)32); // bitmap-format-scanline-unit + r.mData.enqByte((byte)32); // bitmap-format-scanline-pad + r.mData.enqByte((byte)mServer.mKeyboard.minKeycode()); + r.mData.enqByte((byte)mServer.mKeyboard.maxKeycode()); + r.mData.enqSkip(4); + r.mData.enqString(vendor); + r.mData.enqAlign(4); + + for (X11Format fmt : pixmap_formats) { + r.mData.enqByte(fmt.mDepth); + r.mData.enqByte(fmt.mBPP); + r.mData.enqByte(fmt.mPad); + r.mData.enqSkip(5); + } + + mServer.mDefaultScreen.enqueue(r.mData); + + Log.e(XServer.TAG, "Sending setup response"); + try { + mProt.send(r); + } + catch (Exception e) { + Log.e(XServer.TAG, "Exception: "); + e.printStackTrace(); + close(); + } + } + public void onMessage(X11SetupResponse msg) { close(); } + public void onMessage(X11RequestMessage msg) { + while (mServer.mGrabClient != null && mServer.mGrabClient != this) { + Log.d(XServer.TAG, "Client#"+mId+" waiting on server grab"); + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + // Ignore + } + } + ++mSeqNo; + Log.d(XServer.TAG, "Message: seqno=" + mSeqNo + ", name=" + msg.name()); + try { + switch (msg.requestType()) { + case 1: X11Window.create(this, msg); break; + case 2: getWindow(msg.mData.deqInt()).handleChangeWindowAttributes(this, msg); break; + case 3: getWindow(msg.mData.deqInt()).handleGetWindowAttributes(this, msg); break; + case 4: getWindow(msg.mData.deqInt()).handleDestroyWindow(this, msg); break; + case 8: getWindow(msg.mData.deqInt()).handleMapWindow(this, msg); break; + case 9: getWindow(msg.mData.deqInt()).handleMapSubwindows(this, msg); break; + case 10: getWindow(msg.mData.deqInt()).handleUnmapWindow(this, msg); break; + case 11: getWindow(msg.mData.deqInt()).handleUnmapSubwindows(this, msg); break; + case 12: getWindow(msg.mData.deqInt()).handleConfigureWindow(this, msg); break; + case 14: getDrawable(msg.mData.deqInt()).handleGetGeometry(this, msg); break; + case 16: mServer.handleInternAtom(this, msg); break; + case 18: getWindow(msg.mData.deqInt()).handleChangeProperty(this, msg); break; + case 19: getWindow(msg.mData.deqInt()).handleDeleteProperty(this, msg); break; + case 20: getWindow(msg.mData.deqInt()).handleGetProperty(this, msg); break; + case 21: getWindow(msg.mData.deqInt()).handleListProperties(this, msg); break; + case 22: mServer.handleSetSelectionOwner(this, msg); break; + case 23: mServer.handleGetSelectionOwner(this, msg); break; + case 36: mServer.handleGrabServer(this, msg); break; + case 37: mServer.handleUngrabServer(this, msg); break; + case 38: mServer.mKeyboard.handleQueryPointer(this, msg); break; + case 40: getWindow(msg.mData.deqInt()).handleTranslateCoordinates(this, msg); break; + case 42: mServer.handleSetInputFocus(this, msg); break; + case 43: mServer.handleGetInputFocus(this, msg); break; + case 45: mServer.handleOpenFont(this, msg); break; + case 46: getFont(msg.mData.deqInt()).handleCloseFont(this, msg); break; + case 47: getFontable(msg.mData.deqInt()).handleQueryFont(this, msg); break; + case 49: mServer.handleListFonts(this, msg); break; + case 50: mServer.handleListFontsWithInfo(this, msg); break; + case 53: X11Pixmap.create(this, msg); break; + case 54: getPixmap(msg.mData.deqInt()).handleFreePixmap(this, msg); break; + case 55: X11GContext.create(this, msg); break; + case 56: getGContext(msg.mData.deqInt()).handleChangeGC(this, msg); break; + case 59: getGContext(msg.mData.deqInt()).handleSetClipRectangles(this, msg); break; + case 60: getGContext(msg.mData.deqInt()).handleFreeGC(this, msg); break; + case 61: getWindow(msg.mData.deqInt()).handleClearArea(this, msg); break; + case 62: getDrawable(msg.mData.deqInt()).handleCopyArea(this, msg); break; + case 64: getDrawable(msg.mData.deqInt()).handlePolyPoint(this, msg); break; + case 65: getDrawable(msg.mData.deqInt()).handlePolyLine(this, msg); break; + case 66: getDrawable(msg.mData.deqInt()).handlePolySegment(this, msg); break; + case 67: getDrawable(msg.mData.deqInt()).handlePolyRectangle(this, msg); break; + case 68: getDrawable(msg.mData.deqInt()).handlePolyArc(this, msg); break; + case 69: getDrawable(msg.mData.deqInt()).handleFillPoly(this, msg); break; + case 70: getDrawable(msg.mData.deqInt()).handlePolyFillRectangle(this, msg); break; + case 71: getDrawable(msg.mData.deqInt()).handlePolyFillArc(this, msg); break; + case 72: getDrawable(msg.mData.deqInt()).handlePutImage(this, msg); break; + case 73: getDrawable(msg.mData.deqInt()).handleGetImage(this, msg); break; + case 76: getDrawable(msg.mData.deqInt()).handleImageText8(this, msg); break; + case 78: X11Colormap.create(this, msg); break; + case 92: getColormap(msg.mData.deqInt()).handleLookupColor(this, msg); break; + case 93: X11PixmapCursor.create(this, msg); break; + case 94: X11GlyphCursor.create(this, msg); break; + case 95: getCursor(msg.mData.deqInt()).handleFreeCursor(this, msg); break; + case 96: getCursor(msg.mData.deqInt()).handleRecolorCursor(this, msg); break; + case 98: mServer.handleQueryExtension(this, msg); break; + case 101: mServer.mKeyboard.handleGetKeyboardMapping(this, msg); break; + case 104: mServer.mKeyboard.handleBell(this, msg); break; + case 119: mServer.mKeyboard.handleGetModifierMapping(this, msg); break; + default : throw new X11Error(X11Error.IMPLEMENTATION, msg.requestType()); + } + } + catch (X11Error e) { + Log.e(XServer.TAG, "X11Error: " + e.name()); + e.printStackTrace(); + X11ErrorMessage err = new X11ErrorMessage(mProt.mEndian, e.mCode); + err.mData.enqInt(e.mVal); + err.mData.enqShort(msg.requestType() <= 127 ? 0 : msg.headerData()); + err.mData.enqShort(msg.requestType()); + send(err); + close(); //XXX: for debugging + } + catch (Exception e) { + Log.e(XServer.TAG, "Exception: "); + e.printStackTrace(); + close(); + } + } + public void onMessage(X11ReplyMessage msg) { close(); } + public void onMessage(X11EventMessage msg) { close(); } + public void onMessage(X11ErrorMessage msg) { close(); } +} diff --git a/src/tdm/xserver/X11Color.java b/src/tdm/xserver/X11Color.java new file mode 100644 index 0000000..42a410c --- /dev/null +++ b/src/tdm/xserver/X11Color.java @@ -0,0 +1,19 @@ +package tdm.xserver; + +class X11Color +{ + short r; + short g; + short b; + + void set(byte _r, byte _g, byte _b) { + r = (short)(_r << 8 | _r); + g = (short)(_g << 8 | _g); + b = (short)(_b << 8 | _b); + } + void set(short _r, short _g, short _b) { + r = _r; + g = _g; + b = _b; + } +} diff --git a/src/tdm/xserver/X11Colormap.java b/src/tdm/xserver/X11Colormap.java new file mode 100644 index 0000000..a8b4c15 --- /dev/null +++ b/src/tdm/xserver/X11Colormap.java @@ -0,0 +1,124 @@ +package tdm.xserver; + +import android.content.res.AssetManager; +import android.util.Log; + +import java.util.Map; +import java.util.HashMap; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; + +class X11Colormap extends X11Resource +{ + static Map rgbmap; + + static void globalInit(X11Server server) { + rgbmap = new HashMap(); + + BufferedReader br; + String s; + + try { + AssetManager am = server.mContext.getAssets(); + br = new BufferedReader(new InputStreamReader(am.open("rgb.txt"))); + while ((s = br.readLine()) != null) { + if (s.length() == 0 || s.charAt(0) == '#' || s.charAt(0) == '!') { + continue; + } + String[] fields = s.trim().split("[ \t]{1,}", 4); + if (fields.length != 4) { + Log.d("X", "Bad line in rgb.txt: " + s); + continue; + } + X11Color color = new X11Color(); + byte r = (byte)(Short.parseShort(fields[0]) & 0xff); + byte g = (byte)(Short.parseShort(fields[1]) & 0xff); + byte b = (byte)(Short.parseShort(fields[2]) & 0xff); + color.set(r, g, b); + String name = fields[3].toLowerCase(); + rgbmap.put(name, color); + } + } + catch (Exception e) { + Log.e("X", "Failed to init RGB data from assets"); + } + + try { + br = new BufferedReader(new InputStreamReader(new FileInputStream("/data/local/rgb.txt"))); + while ((s = br.readLine()) != null) { + if (s.length() == 0 || s.charAt(0) == '#' || s.charAt(0) == '!') { + continue; + } + String[] fields = s.trim().split("[ \t]{1,}", 4); + if (fields.length != 4) { + Log.d("X", "Bad line in rgb.txt: " + s); + continue; + } + X11Color color = new X11Color(); + byte r = (byte)(Short.parseShort(fields[0]) & 0xff); + byte g = (byte)(Short.parseShort(fields[1]) & 0xff); + byte b = (byte)(Short.parseShort(fields[2]) & 0xff); + color.set(r, g, b); + String name = fields[3].toLowerCase(); + rgbmap.put(name, color); + } + } + catch (Exception e) { + Log.e("X", "Failed to init RGB data from dir"); + e.printStackTrace(); + } + } + + static void create(X11Client c, X11RequestMessage msg) throws X11Error { + int id = msg.mData.deqInt(); + X11Colormap r = new X11Colormap(id); + r.handleCreate(c, msg); + c.addResource(r); + } + + int mVisual; + byte mAlloc; + + X11Colormap(int id) { + super(X11Resource.COLORMAP, id); + } + + void destroy() { + super.destroy(); + } + + void initDefault() { + mVisual = X11Visual.NONE; + mAlloc = (byte)1; // All + } + + void handleCreate(X11Client c, X11RequestMessage msg) { + //XXX: parse and handle window and visual + mVisual = X11Visual.NONE; + mAlloc = (byte)1; // All + } + + void handleLookupColor(X11Client c, X11RequestMessage msg) throws X11Error { + short len = msg.mData.deqShort(); + msg.mData.deqSkip(2); + String name = msg.mData.deqString(len); + Log.d("X", "LookupColor: name=<"+name+">"); + X11Color color = rgbmap.get(name); + if (color == null) { + throw new X11Error(X11Error.NAME, 92 /* LookupColor */); //XXX??? + } + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.mData.enqShort(color.r); + reply.mData.enqShort(color.g); + reply.mData.enqShort(color.b); + reply.mData.enqShort(color.r); + reply.mData.enqShort(color.g); + reply.mData.enqShort(color.b); + c.send(reply); + } +} diff --git a/src/tdm/xserver/X11Cursor.java b/src/tdm/xserver/X11Cursor.java new file mode 100644 index 0000000..de36239 --- /dev/null +++ b/src/tdm/xserver/X11Cursor.java @@ -0,0 +1,103 @@ +package tdm.xserver; + +class X11Cursor extends X11Resource +{ + X11Color mFgColor; + X11Color mBgColor; + + X11Cursor(int id) { + super(X11Resource.CURSOR, id); + } + + void destroy() { + mBgColor = null; + mFgColor = null; + super.destroy(); + } + + void handleFreeCursor(X11Client c, X11RequestMessage msg) { + c.delResource(mId); + } + + void handleRecolorCursor(X11Client c, X11RequestMessage msg) { + doRecolor(msg.mData); + } + + void doRecolor(ByteQueue q) { + mFgColor = new X11Color(); + mFgColor.r = q.deqShort(); + mFgColor.g = q.deqShort(); + mFgColor.b = q.deqShort(); + mBgColor = new X11Color(); + mBgColor.r = q.deqShort(); + mBgColor.g = q.deqShort(); + mBgColor.b = q.deqShort(); + } +} + +class X11PixmapCursor extends X11Cursor +{ + static void create(X11Client c, X11RequestMessage msg) throws X11Error { + int id = msg.mData.deqInt(); + X11PixmapCursor r = new X11PixmapCursor(id); + r.handleCreate(c, msg); + c.addResource(r); + } + + X11Pixmap mSource; + X11Pixmap mMask; + X11Point mHotSpot; + + X11PixmapCursor(int id) { + super(id); + } + + void destroy() { + mHotSpot = null; + mMask = null; + mSource = null; + super.destroy(); + } + + void handleCreate(X11Client c, X11RequestMessage msg) throws X11Error { + mSource = c.getPixmap(msg.mData.deqInt()); + mMask = c.getPixmap(msg.mData.deqInt()); + doRecolor(msg.mData); + mHotSpot = new X11Point(); + mHotSpot.x = msg.mData.deqShort(); + mHotSpot.y = msg.mData.deqShort(); + } +} + +class X11GlyphCursor extends X11Cursor +{ + static void create(X11Client c, X11RequestMessage msg) throws X11Error { + int id = msg.mData.deqInt(); + X11GlyphCursor r = new X11GlyphCursor(id); + r.handleCreate(c, msg); + c.addResource(r); + } + + X11Font mSource; + short mSourceChar; + X11Font mMask; + short mMaskChar; + + X11GlyphCursor(int id) { + super(id); + } + + void destroy() { + mMask = null; + mSource = null; + super.destroy(); + } + + void handleCreate(X11Client c, X11RequestMessage msg) throws X11Error { + mSource = c.getFont(msg.mData.deqInt()); + mMask = c.getFont(msg.mData.deqInt()); + mSourceChar = msg.mData.deqShort(); + mMaskChar = msg.mData.deqShort(); + doRecolor(msg.mData); + } +} diff --git a/src/tdm/xserver/X11Depth.java b/src/tdm/xserver/X11Depth.java new file mode 100644 index 0000000..9f3d782 --- /dev/null +++ b/src/tdm/xserver/X11Depth.java @@ -0,0 +1,14 @@ +package tdm.xserver; + +import java.util.ArrayList; + +class X11Depth +{ + byte depth; + ArrayList visuals; + + X11Depth(byte d) { + depth = d; + visuals = new ArrayList(); + } +} diff --git a/src/tdm/xserver/X11Drawable.java b/src/tdm/xserver/X11Drawable.java new file mode 100644 index 0000000..79bd7c9 --- /dev/null +++ b/src/tdm/xserver/X11Drawable.java @@ -0,0 +1,314 @@ +package tdm.xserver; + +import android.util.Log; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.Paint; + +import java.util.ArrayList; + +abstract class X11Drawable extends X11Resource +{ + byte mDepth; + byte mBPP; + X11Rect mRect; + short mBorderWidth; // This is here for GetGeometry + X11Visual mVisual; // This is here for GetImage + + Bitmap mBitmap; + Canvas mCanvas; + + X11Drawable(int type, int id) { + super(type, id); + } + + void destroy() { + mCanvas = null; + mBitmap = null; + mVisual = null; + mRect = null; + super.destroy(); + } + + void handleGetGeometry(X11Client c, X11RequestMessage msg) { + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData(mDepth); + reply.mData.enqInt(c.mServer.mDefaultScreen.mRoot.mId); + reply.mData.enqShort(mRect.x); + reply.mData.enqShort(mRect.y); + reply.mData.enqShort(mRect.w); + reply.mData.enqShort(mRect.h); + reply.mData.enqShort(mBorderWidth); + c.send(reply); + } + + void handleCopyArea(X11Client c, X11RequestMessage msg) throws X11Error { + X11Drawable dst = c.getDrawable(msg.mData.deqInt()); + X11GContext gc = c.getGContext(msg.mData.deqInt()); + short src_x = msg.mData.deqShort(); + short src_y = msg.mData.deqShort(); + short dst_x = msg.mData.deqShort(); + short dst_y = msg.mData.deqShort(); + short w = msg.mData.deqShort(); + short h = msg.mData.deqShort(); + + //XXX: Use Canvas.drawBitmap with a clip mask? + //XXX: handle window with tiled background + short x, y; + for (y = 0; y < h; ++y) { + for (x = 0; x < w; ++x) { + int pixel = mBitmap.getPixel(src_x + x, src_y + y); + dst.mBitmap.setPixel(dst_x + x, dst_y + y, pixel); + } + } + } + + void handlePolyPoint(X11Client c, X11RequestMessage msg) throws X11Error { + X11GContext gc = c.getGContext(msg.mData.deqInt()); + int count = msg.mData.remain()/4; + float[] points = new float[count*2]; + for (int n = 0; n < count; ++n) { + points[n*2+0] = (float)msg.mData.deqShort(); + points[n*2+1] = (float)msg.mData.deqShort(); + } + mCanvas.drawPoints(points, gc.mPaint); + postRender(); + } + + void handlePolyLine(X11Client c, X11RequestMessage msg) throws X11Error { + byte coord_mode = msg.headerData(); + X11GContext gc = c.getGContext(msg.mData.deqInt()); + int count = msg.mData.remain()/4 - 1; + float[] points = new float[count*4]; + float lastx = (float)msg.mData.deqShort(); + float lasty = (float)msg.mData.deqShort(); + for (int n = 0; n < count; ++n) { + points[n*4+0] = lastx; + points[n*4+1] = lasty; + points[n*4+2] = (float)msg.mData.deqShort(); + points[n*4+3] = (float)msg.mData.deqShort(); + if (coord_mode == 0 /* Origin */) { + lastx = points[n*4+2]; + lasty = points[n*4+3]; + } + else { + lastx += points[n*4+2]; + lasty += points[n*4+3]; + } + } + mCanvas.drawLines(points, gc.mPaint); + postRender(); + } + + void handlePolySegment(X11Client c, X11RequestMessage msg) throws X11Error { + X11GContext gc = c.getGContext(msg.mData.deqInt()); + int count = msg.mData.remain()/8; + float[] points = new float[count*4]; + for (int n = 0; n < count; ++n) { + points[n*4+0] = (float)msg.mData.deqShort(); + points[n*4+1] = (float)msg.mData.deqShort(); + points[n*4+2] = (float)msg.mData.deqShort(); + points[n*4+3] = (float)msg.mData.deqShort(); + } + mCanvas.drawLines(points, gc.mPaint); + postRender(); + } + + void handlePolyRectangle(X11Client c, X11RequestMessage msg) throws X11Error { + X11GContext gc = c.getGContext(msg.mData.deqInt()); + while (msg.mData.remain() > 0) { + short x = msg.mData.deqShort(); + short y = msg.mData.deqShort(); + short w = msg.mData.deqShort(); + short h = msg.mData.deqShort(); + mCanvas.drawRect(x, y, x+w, y+h, gc.mPaint); + } + postRender(); + } + + void handlePolyArc(X11Client c, X11RequestMessage msg) throws X11Error { + X11GContext gc = c.getGContext(msg.mData.deqInt()); + int count = msg.mData.remain()/12; + float[] points = new float[count*6]; + for (int n = 0; n < count; ++n) { + points[n*4+0] = (float)msg.mData.deqShort(); + points[n*4+1] = (float)msg.mData.deqShort(); + points[n*4+2] = (float)msg.mData.deqShort(); + points[n*4+3] = (float)msg.mData.deqShort(); + points[n*4+4] = (float)msg.mData.deqShort(); + points[n*4+5] = (float)msg.mData.deqShort(); + } + throw new X11Error(X11Error.IMPLEMENTATION, 0); + } + + void handleFillPoly(X11Client c, X11RequestMessage msg) throws X11Error { + X11GContext gc = c.getGContext(msg.mData.deqInt()); + byte shape = msg.mData.deqByte(); + byte coord_mode = msg.mData.deqByte(); + msg.mData.deqSkip(2); + + Path path = new Path(); + path.setFillType(Path.FillType.WINDING); //XXX: ??? + + float x, y; + x = (float)msg.mData.deqShort(); + y = (float)msg.mData.deqShort(); + path.moveTo(x, y); + while (msg.mData.remain() > 0) { + x = (float)msg.mData.deqShort(); + y = (float)msg.mData.deqShort(); + path.lineTo(x, y); + } + mCanvas.drawPath(path, gc.mPaint); + postRender(); + } + + void handlePolyFillRectangle(X11Client c, X11RequestMessage msg) throws X11Error { + X11GContext gc = c.getGContext(msg.mData.deqInt()); + + Paint.Style oldstyle = gc.mPaint.getStyle(); + gc.mPaint.setStyle(Paint.Style.FILL); + + while (msg.mData.remain() > 0) { + short x = msg.mData.deqShort(); + short y = msg.mData.deqShort(); + short w = msg.mData.deqShort(); + short h = msg.mData.deqShort(); + mCanvas.drawRect(x, y, x+w, y+h, gc.mPaint); + } + gc.mPaint.setStyle(oldstyle); + postRender(); + } + + void handlePolyFillArc(X11Client c, X11RequestMessage msg) throws X11Error { + throw new X11Error(X11Error.IMPLEMENTATION, msg.requestType()); + } + + void handlePutImage(X11Client c, X11RequestMessage msg) throws X11Error { + byte fmt = msg.headerData(); + X11GContext gc = c.getGContext(msg.mData.deqInt()); + X11Rect rect = new X11Rect(); + rect.w = msg.mData.deqShort(); + rect.h = msg.mData.deqShort(); + rect.x = msg.mData.deqShort(); + rect.y = msg.mData.deqShort(); + byte left_pad = msg.mData.deqByte(); + byte depth = msg.mData.deqByte(); + msg.mData.deqSkip(2); + + if (fmt == 0 /* Bitmap */) { + if (depth != (byte)1) { + throw new X11Error(X11Error.VALUE, depth); + } + fmt = (byte)1 /* XYPixmap */; + } + + ArrayList formats = c.mServer.getPixmapFormats(); + X11Format f = null; + for (X11Format cur : formats) { + if (cur.mDepth == depth) { + f = cur; + break; + } + } + //XXX: handle not found + + Bitmap bmp; + + switch (fmt) { + case 1 /* XYPixmap */: + bmp = f.decodeImageXY(rect.w, rect.h, msg.mData); + break; + case 2 /* ZPixmap */ : + bmp = f.decodeImageZ(rect.w, rect.h, msg.mData); + break; + default: + throw new X11Error(X11Error.VALUE, fmt); + } + + mCanvas.drawBitmap(bmp, rect.x, rect.y, null); + postRender(rect); + } + + void handleGetImage(X11Client c, X11RequestMessage msg) throws X11Error { + byte fmt = msg.headerData(); + X11Rect rect = new X11Rect(); + rect.x = msg.mData.deqShort(); + rect.y = msg.mData.deqShort(); + rect.w = msg.mData.deqShort(); + rect.h = msg.mData.deqShort(); + int plane_mask = msg.mData.deqInt(); + + ArrayList formats = c.mServer.getPixmapFormats(); + X11Format f = null; + for (X11Format cur : formats) { + if (cur.mDepth == mDepth) { + f = cur; + break; + } + } + //XXX: handle not found + + byte[] data; + + switch (fmt) { + case 1 /* XYPixmap */: + data = f.encodeImageXY(rect, plane_mask, mBitmap); + break; + case 2 /* ZPixmap */ : + data = f.encodeImageZ(rect, plane_mask, mBitmap); + break; + default: + throw new X11Error(X11Error.VALUE, fmt); + } + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData(mDepth); + //XXX: This looks ugly, is it the best way? + reply.mData.enqInt( (mVisual == null ? 0 : mVisual.mId) ); + reply.mData.enqSkip(20); + reply.mData.enqArray(data); + c.send(reply); + } + + void handleImageText8(X11Client c, X11RequestMessage msg) throws X11Error { + byte len = msg.headerData(); + X11GContext gc = c.getGContext(msg.mData.deqInt()); + short x = msg.mData.deqShort(); + short y = msg.mData.deqShort(); + String text = msg.mData.deqString(len); + + short x_min, x_max, y_min, y_max; + x_min = x; + x_max = x; + y_min = y; + y_max = y; + for (int idx = 0; idx < text.length(); ++idx) { + char ch = text.charAt(idx); + X11CharInfo info = gc.mFont.getCharInfo(ch); + Bitmap bmp = gc.mFont.getCharImage(ch, gc.mForePixel, gc.mBackPixel); + if (bmp != null) { + //XXX: This is probably not correct + mCanvas.drawBitmap(bmp, x, y-bmp.getHeight(), null); + } + x += info.character_width; + + x_max += info.character_width; + y_min = (short)Math.min(y_min, y-bmp.getHeight()); + //y_max = (short)Math.max(y_max, y+info.descent); + } + + X11Rect r = new X11Rect(); + r.x = x_min; + r.w = (short)(x_max - x_min); + r.y = y_min; + r.h = (short)(y_max - y_min); + + postRender(r); + } + + void postRender(X11Rect r) {} + void postRender() {} +} diff --git a/src/tdm/xserver/X11Error.java b/src/tdm/xserver/X11Error.java new file mode 100644 index 0000000..de88419 --- /dev/null +++ b/src/tdm/xserver/X11Error.java @@ -0,0 +1,54 @@ +package tdm.xserver; + +class X11Error extends Throwable +{ + static final byte NONE = (byte)0; + static final byte REQUEST = (byte)1; + static final byte VALUE = (byte)2; + static final byte WINDOW = (byte)3; + static final byte PIXMAP = (byte)4; + static final byte ATOM = (byte)5; + static final byte CURSOR = (byte)6; + static final byte FONT = (byte)7; + static final byte MATCH = (byte)8; + static final byte DRAWABLE = (byte)9; + static final byte ACCESS = (byte)10; + static final byte ALLOC = (byte)11; + static final byte COLORMAP = (byte)12; + static final byte GCONTEXT = (byte)13; + static final byte IDCHOICE = (byte)14; + static final byte NAME = (byte)15; + static final byte LENGTH = (byte)16; + static final byte IMPLEMENTATION = (byte)17; + + static final String[] error_names = { + "NONE", + "Request", + "Value", + "Window", + "Pixmap", + "Atom", + "Cursor", + "Font", + "Match", + "Drawable", + "Access", + "Alloc", + "Colormap", + "GContext", + "IDChoice", + "Name", + "Length", + "Implementation" + }; + + String name() { return error_names[mCode]; } + + byte mCode; + int mVal; + + X11Error(byte code, int val) { + mCode = code; + mVal = val; + } +} diff --git a/src/tdm/xserver/X11ErrorMessage.java b/src/tdm/xserver/X11ErrorMessage.java new file mode 100644 index 0000000..8144446 --- /dev/null +++ b/src/tdm/xserver/X11ErrorMessage.java @@ -0,0 +1,57 @@ +package tdm.xserver; + +import java.nio.ByteOrder; + +class X11ErrorMessage extends X11Message +{ + static final String[] message_names = { + "NONE", // 0 + "Request", + "Value", + "Window", + "Pixmap", + "Atom", + "Cursor", + "Font", + "Match", + "Drawable", + "Access", // 10 + "Alloc", + "Colormap", + "GContext", + "IDChoice", + "Name", + "Length", + "Implementation" + }; + + String name() { return message_names[mHeaderData]; } + + byte mHeaderData; + short mSeqNo; + + X11ErrorMessage(ByteOrder endian, byte code) { + super(endian); + mHeaderData = code; + } + + void read(ByteQueue q) { + byte event_type = q.deqByte(); // 0x00 + mHeaderData = q.deqByte(); + mSeqNo = q.deqShort(); + mData = q.deqData(28); + } + void write(ByteQueue q) { + q.enqByte((byte)0x00); + q.enqByte(mHeaderData); + q.enqShort(mSeqNo); + q.enqData(mData); + q.enqSkip(28 - mData.pos()); + } + void dispatch(X11ProtocolHandler h) { h.onMessage(this); } + + void headerData(byte val) { mHeaderData = val; } + byte headerData() { return mHeaderData; } + void seqno(short val) { mSeqNo = val; } + short seqno() { return mSeqNo; } +} diff --git a/src/tdm/xserver/X11Event.java b/src/tdm/xserver/X11Event.java new file mode 100644 index 0000000..8e7e0a4 --- /dev/null +++ b/src/tdm/xserver/X11Event.java @@ -0,0 +1,40 @@ +package tdm.xserver; + +class X11Event +{ + static final byte ERROR = 0; // pseudo-event + static final byte REPLY = 1; // pseudo-event + static final byte KEY_PRESS = 2; + static final byte KEY_RELEASE = 3; + static final byte BUTTON_PRESS = 4; + static final byte BUTTON_RELEASE = 5; + static final byte MOTION_NOTIFY = 6; + static final byte ENTER_NOTIFY = 7; + static final byte LEAVE_NOTIFY = 8; + static final byte FOCUS_IN = 9; + static final byte FOCUS_OUT = 10; + static final byte KEYMAP_NOTIFY = 11; + static final byte EXPOSE = 12; + static final byte GRAPHICS_EXPOSE = 13; + static final byte NO_EXPOSE = 14; + static final byte VISIBILITY_NOTIFY = 15; + static final byte CREATE_NOTIFY = 16; + static final byte DESTROY_NOTIFY = 17; + static final byte UNMAP_NOTIFY = 18; + static final byte MAP_NOTIFY = 19; + static final byte MAP_REQUEST = 20; + static final byte REPARENT_NOTIFY = 21; + static final byte CONFIGURE_NOTIFY = 22; + static final byte CONFIGURE_REQUEST = 23; + static final byte GRAVITY_NOTIFY = 24; + static final byte RESIZE_REQUEST = 25; + static final byte CIRCULATE_NOTIFY = 26; + static final byte CIRCULATE_REQUEST = 27; + static final byte PROPERTY_NOTIFY = 28; + static final byte SELECTION_CLEAR = 29; + static final byte SELECTION_REQUEST = 30; + static final byte SELECTION_NOTIFY = 31; + static final byte COLORMAP_NOTIFY = 32; + static final byte CLIENT_MESSAGE = 33; + static final byte MAPPING_NOTIFY = 34; +} diff --git a/src/tdm/xserver/X11EventMessage.java b/src/tdm/xserver/X11EventMessage.java new file mode 100644 index 0000000..fe2789f --- /dev/null +++ b/src/tdm/xserver/X11EventMessage.java @@ -0,0 +1,76 @@ +package tdm.xserver; + +import java.nio.ByteOrder; + +class X11EventMessage extends X11Message +{ + static final String[] message_names = { + "NONE", + "NONE", + "KeyPress", + "KeyRelease", + "ButtonPress", + "ButtonRelease", + "MotionNotify", + "EnterNotify", + "LeaveNotify", + "FocusIn", + "FocusOut", // 10 + "KeymapNotify", + "Expose", + "GraphicsExposure", + "NoExposure", + "VisibilityNotify", + "CreateNotify", + "DestroyNotify", + "UnmapNotify", + "MapNotify", + "MapRequest", // 20 + "ReparentNotify", + "ConfigureNotify", + "ConfigureRequest", + "GravityNotify", + "ResizeRequest", + "CirculateNotify", + "CirculateRequest", + "PropertyNotify", + "SelectionClear", + "SelectionRequest", // 30 + "SelectionNotify", + "ColormapNotify", + "ClientMessage", + "MappingNotify" + }; + + String name() { return message_names[mEventType]; } + + byte mEventType; + byte mHeaderData; + short mSeqNo; + + X11EventMessage(ByteOrder endian, byte evtype) { + super(endian); + mEventType = evtype; + mData.resize(28); + } + + void read(ByteQueue q) { + mEventType = q.deqByte(); + mHeaderData = q.deqByte(); + mSeqNo = q.deqShort(); + mData = q.deqData(28); + } + void write(ByteQueue q) { + q.enqByte(mEventType); + q.enqByte(mHeaderData); + q.enqShort(mSeqNo); + q.enqData(mData); + q.enqSkip(28 - mData.pos()); + } + void dispatch(X11ProtocolHandler h) { h.onMessage(this); } + + void headerData(byte val) { mHeaderData = val; } + byte headerData() { return mHeaderData; } + void seqno(short val) { mSeqNo = val; } + short seqno() { return mSeqNo; } +} diff --git a/src/tdm/xserver/X11Font.java b/src/tdm/xserver/X11Font.java new file mode 100644 index 0000000..d574494 --- /dev/null +++ b/src/tdm/xserver/X11Font.java @@ -0,0 +1,103 @@ +package tdm.xserver; + +import android.util.Log; + +import android.graphics.Bitmap; +import android.graphics.Color; + +import java.util.HashMap; +import java.util.Map; + +class X11Font extends X11Fontable +{ + class CacheKey { + char ch; + int fore; + int back; + + CacheKey(char c, int f, int b) { ch = c; fore = f; back = b; } + public int hashCode() { return ((ch << 24) | (fore ^ back)); } + public boolean equals(Object obj) { + CacheKey other = (CacheKey)obj; + return (other.ch == ch && other.fore == fore && other.back == back); + } + } + + FontData mData; + Map mImageCache; + + X11Font(int id, FontData data) { + super(X11Resource.FONT, id); + mData = data; + mImageCache = new HashMap(); + } + + void destroy() { + mImageCache = null; + mData = null; + super.destroy(); + } + + void handleCloseFont(X11Client c, X11RequestMessage msg) { + c.delResource(mId); + } + + void handleQueryFont(X11Client c, X11RequestMessage msg) { + X11ReplyMessage reply = new X11ReplyMessage(msg); + mData.enqueueInfo(reply.mData); + mData.enqueueCharInfoCount(reply.mData); + mData.enqueueProperties(reply.mData); + mData.enqueueCharInfo(reply.mData); + c.send(reply); + } + + X11CharInfo getCharInfo(char ch) { + return mData.mCharInfos.get(ch); + } + + Bitmap getCharImage(char ch, int fore, int back) { + CacheKey key = new CacheKey(ch, fore, back); + Bitmap bmp = mImageCache.get(key); + if (bmp == null) { + Bitmap glyph = mData.getBitmap(ch); + if (glyph == null) { + return null; + } + int w = glyph.getWidth(); + int h = glyph.getHeight(); + bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + int x, y; + for (y = 0; y < h; ++y) { + for (x = 0; x < w; ++x) { + int pixel = glyph.getPixel(x, y); + bmp.setPixel(x, y, (pixel == Color.BLACK ? back : fore)); + } + } + mImageCache.put(key, bmp); + } + return bmp; + } + + void showBitmap(char ch) { + Log.d("X", "Show bitmap for <"+ch+">..."); + X11CharInfo info = getCharInfo(ch); + Bitmap bmp = mData.getBitmap(ch); + if (bmp == null) { + Log.d("X", " (null)"); + return; + } + Log.d("X", " w="+bmp.getWidth()+", h="+bmp.getHeight()+", a="+info.ascent+", d="+info.descent+" ..."); + for (int y = 0; y < bmp.getHeight(); ++y) { + StringBuffer buf = new StringBuffer(); + for (int x = 0; x < bmp.getWidth(); ++x) { + if (bmp.getPixel(x, y) == Color.BLACK) { + buf.append("."); + } + else { + buf.append("X"); + } + } + Log.d("X", " " + buf); + } + } +} diff --git a/src/tdm/xserver/X11FontProp.java b/src/tdm/xserver/X11FontProp.java new file mode 100644 index 0000000..d3fe44c --- /dev/null +++ b/src/tdm/xserver/X11FontProp.java @@ -0,0 +1,7 @@ +package tdm.xserver; + +class X11FontProp +{ + int name; + int value; +} diff --git a/src/tdm/xserver/X11Fontable.java b/src/tdm/xserver/X11Fontable.java new file mode 100644 index 0000000..195498a --- /dev/null +++ b/src/tdm/xserver/X11Fontable.java @@ -0,0 +1,14 @@ +package tdm.xserver; + +abstract class X11Fontable extends X11Resource +{ + X11Fontable(int t, int n) { + super(t, n); + } + + void destroy() { + super.destroy(); + } + + abstract void handleQueryFont(X11Client c, X11RequestMessage msg); +} diff --git a/src/tdm/xserver/X11Format.java b/src/tdm/xserver/X11Format.java new file mode 100644 index 0000000..a210e80 --- /dev/null +++ b/src/tdm/xserver/X11Format.java @@ -0,0 +1,332 @@ +package tdm.xserver; + +import android.graphics.Bitmap; + +abstract class X11Format +{ + byte mDepth; + byte mBPP; + byte mPad; + + X11Format(byte d, byte b, byte p) { + mDepth = d; + mBPP = b; + mPad = p; + } + + protected void decodePlane(short w, short h, byte[] buf, byte plane, Bitmap bmp) { + int plane_shift = (mDepth - plane); + int bytes_per_row = MathX.divceil(w, 8); + int bytes_per_plane = h * bytes_per_row; + short y, x; + int plane_off, y_off, x_off; + byte val; + + plane_off = plane * bytes_per_plane; + for (y = 0; y < h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < w; ++x) { + x_off = x/8; + val = buf[plane_off + y_off + x_off]; + int shift = (7 - (x%8)); + + int tmp = bmp.getPixel(x, y); + tmp |= ((val >> shift) & 0x1) << plane_shift; + bmp.setPixel(x, y, tmp); + } + } + } + + Bitmap decodeImageXY(short w, short h, ByteQueue q) { + byte[] buf = q.deqArray(w*h*mBPP/8); //XXX + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + byte plane; + for (plane = 0; plane < mBPP; ++plane) { + decodePlane(w, h, buf, plane, bmp); + } + return bmp; + } + + abstract Bitmap decodeImageZ(short w, short h, ByteQueue q); + + protected void encodePlane(X11Rect r, Bitmap bmp, byte plane, byte[] buf) { + int plane_shift = (mDepth - plane); + int bytes_per_row = MathX.divceil(r.w, 8); + int bytes_per_plane = r.h * bytes_per_row; + short y, x; + int plane_off, y_off, x_off; + int val; + + plane_off = plane * bytes_per_plane; + for (y = 0; y < r.h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < r.w; ++x) { + x_off = x/8; + val = bmp.getPixel(r.x + x, r.y + y); + int shift = (7 - (x%8)); + byte v; + v = buf[plane_off + y_off + x_off]; + v |= ((val >> plane_shift) & 0x1) << shift; + buf[plane_off + y_off + x_off] = v; + } + } + } + + byte[] encodeImageXY(X11Rect r, int plane_mask, Bitmap bmp) { + byte[] buf = new byte[r.w*r.h*mBPP/8]; //XXX + byte plane; + for (plane = 0; plane < mBPP; ++plane) { + if ((plane_mask & (1 << plane)) != 0) { + encodePlane(r, bmp, plane, buf); + } + } + return buf; + } + + abstract byte[] encodeImageZ(X11Rect r, int plane_mask, Bitmap bmp); +} + +class X11Format_1 extends X11Format +{ + X11Format_1() { super((byte)1, (byte)1, (byte)8); } + + Bitmap decodeImageZ(short w, short h, ByteQueue q) { + int bytes_per_row = MathX.divceil(w, 8); + byte[] buf = q.deqArray(bytes_per_row*h); + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + decodePlane(w, h, buf, (byte)0, bmp); + return bmp; + } + + byte[] encodeImageZ(X11Rect r, int plane_mask, Bitmap bmp) { + int bytes_per_row = MathX.divceil(r.w, 8); + byte[] buf = new byte[bytes_per_row*r.h]; + if ((plane_mask & 1) != 0) { + encodePlane(r, bmp, (byte)0, buf); + } + return buf; + } +} + +class X11Format_4 extends X11Format +{ + X11Format_4() { super((byte)4, (byte)4, (byte)8); } + + Bitmap decodeImageZ(short w, short h, ByteQueue q) { + int bytes_per_row = MathX.divceil(w, 2); + byte[] buf = q.deqArray(bytes_per_row*h); + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < w; ++x) { + x_off = x/2; + val = buf[y_off + x_off]; + bmp.setPixel(x, y, (val >> (4*(1-x%2))) & 0xf); + } + } + return bmp; + } + + byte[] encodeImageZ(X11Rect r, int plane_mask, Bitmap bmp) { + int bytes_per_row = MathX.divceil(r.w, 2); + byte[] buf = new byte[bytes_per_row*r.h]; + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < r.h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < r.w; ++x) { + x_off = x/2; + val = bmp.getPixel(r.x + x, r.y + y) & 0xf; + val &= plane_mask; + buf[y_off + x_off] = (byte)(val << 4*(1-x%2)); + } + } + return buf; + } +} + +class X11Format_8 extends X11Format +{ + X11Format_8() { super((byte)8, (byte)8, (byte)8); } + + Bitmap decodeImageZ(short w, short h, ByteQueue q) { + byte[] buf = q.deqArray(w*h); + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < h; ++y) { + y_off = y * w; + for (x = 0; x < w; ++x) { + x_off = x; + val = buf[y_off + x_off]; + bmp.setPixel(x, y, val); + } + } + return bmp; + } + + byte[] encodeImageZ(X11Rect r, int plane_mask, Bitmap bmp) { + int bytes_per_row = r.w; + byte[] buf = new byte[bytes_per_row*r.h]; + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < r.h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < r.w; ++x) { + x_off = x; + val = bmp.getPixel(r.x + x, r.y + y); + val &= plane_mask; + buf[y_off + x_off] = (byte)val; + } + } + return buf; + } +} + +class X11Format_16 extends X11Format +{ + X11Format_16() { super((byte)16, (byte)16, (byte)16); } + + Bitmap decodeImageZ(short w, short h, ByteQueue q) { + int bytes_per_row = w*2; + byte[] buf = q.deqArray(bytes_per_row*h); + //XXX: This is wrong. Use RGB_565 and copyPixelsFromBuffer? + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < w; ++x) { + x_off = x*2; + val = (buf[y_off + x_off + 0] << 8) | + buf[y_off + x_off + 1]; + bmp.setPixel(x, y, val); + } + } + return bmp; + } + + byte[] encodeImageZ(X11Rect r, int plane_mask, Bitmap bmp) { + int bytes_per_row = r.w*2; + byte[] buf = new byte[bytes_per_row*r.h]; + //XXX: This is wrong. Use RGB_565 and copyPixelsToBuffer? + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < r.h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < r.w; ++x) { + x_off = x*2; + val = bmp.getPixel(r.x + x, r.y + y); + val &= plane_mask; + buf[y_off + x_off + 0] = (byte)(val >> 8); + buf[y_off + x_off + 1] = (byte)val; + } + } + return buf; + } +} + +class X11Format_24 extends X11Format +{ + X11Format_24() { super((byte)24, (byte)32, (byte)32); } + + Bitmap decodeImageZ(short w, short h, ByteQueue q) { + int bytes_per_row = w*4; + byte[] buf = q.deqArray(bytes_per_row*h); + //XXX: Use copyPixelsFromBuffer? + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < w; ++x) { + x_off = x*4; + val = (buf[y_off + x_off + 1] << 16) | + (buf[y_off + x_off + 2] << 8) | + buf[y_off + x_off + 3]; + bmp.setPixel(x, y, 0xff000000 | val); + } + } + return bmp; + } + + byte[] encodeImageZ(X11Rect r, int plane_mask, Bitmap bmp) { + int bytes_per_row = r.w*4; + byte[] buf = new byte[bytes_per_row*r.h]; + //XXX: Use copyPixelsToBuffer? + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < r.h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < r.w; ++x) { + x_off = x*4; + val = bmp.getPixel(r.x + x, r.y + y); + val &= plane_mask; + buf[y_off + x_off + 0] = (byte)0; + buf[y_off + x_off + 1] = (byte)(val >> 16); + buf[y_off + x_off + 2] = (byte)(val >> 8); + buf[y_off + x_off + 3] = (byte)val; + } + } + return buf; + } +} + +class X11Format_32 extends X11Format +{ + X11Format_32() { super((byte)32, (byte)32, (byte)32); } + + Bitmap decodeImageZ(short w, short h, ByteQueue q) { + int bytes_per_row = w*4; + byte[] buf = q.deqArray(bytes_per_row*h); + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < w; ++x) { + x_off = x*4; + //XXX: what about alpha? + val = (buf[y_off + x_off + 0] << 24) | + (buf[y_off + x_off + 1] << 16) | + (buf[y_off + x_off + 2] << 8) | + buf[y_off + x_off + 3]; + bmp.setPixel(x, y, val); + } + } + return bmp; + } + + byte[] encodeImageZ(X11Rect r, int plane_mask, Bitmap bmp) { + int bytes_per_row = r.w*4; + byte[] buf = new byte[bytes_per_row*r.h]; + short y, x; + int y_off, x_off; + int val; + for (y = 0; y < r.h; ++y) { + y_off = y * bytes_per_row; + for (x = 0; x < r.w; ++x) { + x_off = x*4; + //XXX: what about alpha? + val = bmp.getPixel(r.x + x, r.y + y); + val &= plane_mask; + buf[y_off + x_off + 0] = (byte)(val >> 24); + buf[y_off + x_off + 1] = (byte)(val >> 16); + buf[y_off + x_off + 2] = (byte)(val >> 8); + buf[y_off + x_off + 3] = (byte)val; + } + } + return buf; + } +} diff --git a/src/tdm/xserver/X11GContext.java b/src/tdm/xserver/X11GContext.java new file mode 100644 index 0000000..b37ce4b --- /dev/null +++ b/src/tdm/xserver/X11GContext.java @@ -0,0 +1,179 @@ +package tdm.xserver; + +import android.util.Log; + +import android.graphics.Paint; +import android.graphics.Path; + +class X11GContext extends X11Fontable +{ + static void create(X11Client c, X11RequestMessage msg) throws X11Error { + int id = msg.mData.deqInt(); + X11GContext r = new X11GContext(id); + r.handleCreate(c, msg); + c.addResource(r); + } + + Paint mPaint; + + X11Drawable mDrawable; + byte mFunction; + int mPlaneMask; + int mForePixel; + int mBackPixel; + short mLineWidth; + byte mLineStyle; + byte mCapStyle; + byte mJoinStyle; + byte mFillStyle; + byte mFillRule; + X11Pixmap mTile; + X11Pixmap mStipple; + X11Point mTileStippleOrigin; + X11Font mFont; + byte mSubWindowMode; + byte mGraphicsExposures; + X11Point mClipOrigin; + X11Pixmap mClipMask; + Path mClipPath; + short mDashOffset; + byte mDashes; + byte mArcMode; + + X11GContext(int n) { + super(X11Resource.GCONTEXT, n); + mPaint = new Paint(); + mForePixel = 0xff000000; + mPaint.setColor(mForePixel); + mBackPixel = 0xffffffff; + mTileStippleOrigin = new X11Point(); + } + + void destroy() { + mClipPath = null; + mClipMask = null; + mClipOrigin = null; + mFont = null; + mTileStippleOrigin = null; + mStipple = null; + mTile = null; + mDrawable = null; + mPaint = null; + super.destroy(); + } + + void handleQueryFont(X11Client c, X11RequestMessage msg) { + mFont.handleQueryFont(c, msg); + } + + void handleCreate(X11Client c, X11RequestMessage msg) throws X11Error { + int id = msg.mData.deqInt(); + mDrawable = c.getDrawable(id); + doChangeAttributes(c, msg.mData); + } + + void handleChangeGC(X11Client c, X11RequestMessage msg) throws X11Error { + doChangeAttributes(c, msg.mData); + } + + void handleSetClipRectangles(X11Client c, X11RequestMessage msg) { + byte ordering = msg.headerData(); + mClipOrigin = new X11Point(); + mClipOrigin.x = msg.mData.deqShort(); + mClipOrigin.y = msg.mData.deqShort(); + mClipPath = new Path(); + while (msg.mData.remain() > 0) { + short x = msg.mData.deqShort(); + short y = msg.mData.deqShort(); + short w = msg.mData.deqShort(); + short h = msg.mData.deqShort(); + mClipPath.addRect(x, y, x+w, y+h, Path.Direction.CW); + } + } + + void handleFreeGC(X11Client c, X11RequestMessage msg) { + c.delResource(mId); + } + + private void doChangeAttributes(X11Client c, ByteQueue q) throws X11Error { + int mask = q.deqInt(); + int val; + + if ((mask & 0x000001) != 0) { // function + mFunction = (byte)q.deqInt(); + } + if ((mask & 0x000002) != 0) { // plane-mask + mPlaneMask = q.deqInt(); + } + if ((mask & 0x000004) != 0) { // foreground + mForePixel = (q.deqInt() | 0xff000000); + mPaint.setColor(mForePixel); + } + if ((mask & 0x000008) != 0) { // background + mBackPixel = (q.deqInt() | 0xff000000); + } + if ((mask & 0x000010) != 0) { // line-width + mLineWidth = (short)q.deqInt(); + } + if ((mask & 0x000020) != 0) { // line-style + mLineStyle = (byte)q.deqInt(); + } + if ((mask & 0x000040) != 0) { // cap-style + mCapStyle = (byte)q.deqInt(); + } + if ((mask & 0x000080) != 0) { // join-style + mJoinStyle = (byte)q.deqInt(); + } + if ((mask & 0x000100) != 0) { // fill-style + mFillStyle = (byte)q.deqInt(); + } + if ((mask & 0x000200) != 0) { // fill-rule + mFillRule = (byte)q.deqInt(); + } + if ((mask & 0x000400) != 0) { // tile + mTile = c.getPixmap(q.deqInt()); + } + if ((mask & 0x000800) != 0) { // stipple + mStipple = c.getPixmap(q.deqInt()); + } + if ((mask & 0x001000) != 0) { // tile-stipple-x-origin + mTileStippleOrigin.x = (short)q.deqInt(); + } + if ((mask & 0x002000) != 0) { // tile-stipple-y-origin + mTileStippleOrigin.y = (short)q.deqInt(); + } + if ((mask & 0x004000) != 0) { // font + mFont = c.getFont(q.deqInt()); + } + if ((mask & 0x008000) != 0) { // subwindow-mode + mSubWindowMode = (byte)q.deqInt(); + } + if ((mask & 0x010000) != 0) { // graphics-exposures + mGraphicsExposures = (byte)q.deqInt(); + } + if ((mask & 0x020000) != 0) { // clip-x-origin + mClipOrigin.x = (short)q.deqInt(); + } + if ((mask & 0x040000) != 0) { // clip-y-origin + mClipOrigin.y = (short)q.deqInt(); + } + if ((mask & 0x080000) != 0) { // clip-mask + val = q.deqInt(); + if (val == X11Resource.NONE) { + mClipMask = null; + } + else { + mClipMask = c.getPixmap(val); + } + } + if ((mask & 0x100000) != 0) { // dash-offset + mDashOffset = (short)q.deqInt(); + } + if ((mask & 0x200000) != 0) { // dashes + mDashes = (byte)q.deqInt(); + } + if ((mask & 0x400000) != 0) { // arc-mode + mArcMode = (byte)q.deqInt(); + } + } +} diff --git a/src/tdm/xserver/X11Keyboard.java b/src/tdm/xserver/X11Keyboard.java new file mode 100644 index 0000000..a5c07a1 --- /dev/null +++ b/src/tdm/xserver/X11Keyboard.java @@ -0,0 +1,279 @@ +package tdm.xserver; + +import android.util.Log; + +import android.media.AudioFormat; +import android.media.AudioTrack; +import android.media.AudioManager; +import android.view.KeyEvent; + +import java.util.ArrayList; + +class X11Keyboard +{ + static final byte NO_SYMBOL = 0; + + static final int X_MIN_KEYCODE = 8; + + static final int NUM_KEYCODES = 100; + static final int NUM_MODIFIERS = 8; + + /* + * NB: Many keycodes are undefined in v2.3. + * (ESCAPE, Fn keys, numpad, etc.) + */ + static final int[][] key_codes = { + { 0, 0 }, // 0 + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, // KEYCODE_CALL + { 0, 0 }, // KEYCODE_ENDCALL + { '0', ')' }, // KEYCODE_0 + { '1', '!' }, // KEYCODE_1 + { '2', '@' }, // KEYCODE_2 + { '3', '#' }, // KEYCODE_3 10 + { '4', '$' }, // KEYCODE_4 + { '5', '%' }, // KEYCODE_5 + { '6', '^' }, // KEYCODE_6 + { '7', '&' }, // KEYCODE_7 + { '8', '*' }, // KEYCODE_8 + { '9', '(' }, // KEYCODE_9 + { '*', '*' }, // KEYCODE_STAR (XXX: see KEYCODE_8) + { '#', '#' }, // KEYCODE_POUND (XXX: see KEYCODE_3) + { 0, 0 }, // KEYCODE_DPAD_UP + { 0, 0 }, // KEYCODE_DPAD_DOWN 20 + { 0, 0 }, // KEYCODE_DPAD_LEFT + { 0, 0 }, // KEYCODE_DPAD_RIGHT + { 0xffe3, 0 }, // KEYCODE_DPAD_CENTER (=> L.CTRL) + { 0, 0 }, // KEYCODE_VOLUME_UP + { 0, 0 }, // KEYCODE_VOLUME_DOWN + { 0, 0 }, // KEYCODE_POWER + { 0, 0 }, // KEYCODE_CAMERA + { 0, 0 }, // KEYCODE_CLEAR + { 'a', 'A' }, // KEYCODE_A + { 'b', 'B' }, // KEYCODE_B 30 + { 'c', 'C' }, // KEYCODE_C + { 'd', 'D' }, // KEYCODE_D + { 'e', 'E' }, // KEYCODE_E + { 'f', 'F' }, // KEYCODE_F + { 'g', 'G' }, // KEYCODE_G + { 'h', 'H' }, // KEYCODE_H + { 'i', 'I' }, // KEYCODE_I + { 'j', 'J' }, // KEYCODE_J + { 'k', 'K' }, // KEYCODE_K + { 'l', 'L' }, // KEYCODE_L 40 + { 'm', 'M' }, // KEYCODE_M + { 'n', 'N' }, // KEYCODE_N + { 'o', 'O' }, // KEYCODE_O + { 'p', 'P' }, // KEYCODE_P + { 'q', 'Q' }, // KEYCODE_Q + { 'r', 'R' }, // KEYCODE_R + { 's', 'S' }, // KEYCODE_S + { 't', 'T' }, // KEYCODE_T + { 'u', 'U' }, // KEYCODE_U + { 'v', 'V' }, // KEYCODE_V 50 + { 'w', 'W' }, // KEYCODE_W + { 'x', 'X' }, // KEYCODE_X + { 'y', 'Y' }, // KEYCODE_Y + { 'z', 'Z' }, // KEYCODE_Z + { ',', '<' }, // KEYCODE_COMMA + { '.', '>' }, // KEYCODE_PERIOD + { 0x0101, 0 }, // KEYCODE_ALT_LEFT + { 0x0102, 0 }, // KEYCODE_ALT_RIGHT + { 0x0113, 0 }, // KEYCODE_SHIFT_LEFT + { 0x0114, 0 }, // KEYCODE_SHIFT_RIGHT 60 + { 0xff09, 0xff09 }, // KEYCODE_TAB + { ' ', ' ' }, // KEYCODE_SPACE + { 0, 0 }, // KEYCODE_SYM + { 0, 0 }, + { 0, 0 }, // KEYCODE_ENVELOPE + { 0xff0d, 0xff0d }, // KEYCODE_ENTER + { 0xff08, 0xff08 }, // KEYCODE_DEL (backspace) + { '`', '~' }, // KEYCODE_GRAVE + { '-', '_' }, // KEYCODE_MINUS + { '=', '+' }, // KEYCODE_EQUALS 70 + { '[', '{' }, // KEYCODE_LEFT_BRACKET + { ']', '}' }, // KEYCODE_RIGHT_BRACKET + { '\\', '|' }, // KEYCODE_BACKSLASH + { ';', ':' }, // KEYCODE_SEMICOLON + { '\'', '"' }, // KEYCODE_APOSTROPHE + { '/', '?' }, // KEYCODE_SLASH + { '@', '@' }, // KEYCODE_AT (XXX: see KEYCODE_2) + { 0, 0 }, // KEYCODE_NUM (XXX: not numlock, see docs) + { 0, 0 }, // KEYCODE_HEADSETHOOK + { 0, 0 }, // KEYCODE_FOCUS 80 + { '+', '+' }, // KEYCODE_PLUS (XXX: see KEYCODE_EQUALS) + { 0, 0 }, // KEYCODE_MENU + { 0, 0 }, // KEYCODE_NOTIFICATION + { 0, 0 }, // KEYCODE_SEARCH + { 0, 0 }, // KEYCODE_MEDIA_PLAY_PAUSE + { 0, 0 }, // KEYCODE_MEDIA_STOP + { 0, 0 }, // KEYCODE_MEDIA_NEXT + { 0, 0 }, // KEYCODE_MEDIA_PREVIOUS + { 0, 0 }, // KEYCODE_MEDIA_REWIND + { 0, 0 }, // KEYCODE_MEDIA_FAST_FORWARD 90 + { 0, 0 }, // KEYCODE_MUTE + { 0x0111, 0 }, // KEYCODE_PAGE_UP + { 0x0110, 0 }, // KEYCODE_PAGE_DOWN + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 } // 100 + }; + + static final int[][] mod_map = { + { 59, 60 }, // Shift = KEYCODE_SHIFT_LEFT, KEYCODE_SHIFT_RIGHT + { 0, 0 }, // Lock = (none) + { 23, 0 }, // Control = KEYCODE_DPAD_CENTER, (none) + { 57, 58 }, // Mod1 = KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT + { 0, 0 }, // Mod2 = (none) + { 0, 0 }, // Mod3 = (none) + { 0, 0 }, // Mod4 = (none) + { 0, 0 }, // Mod5 = (none) + }; + + byte mGlobalAutoRepeat; + byte mKeyClickPercent; + + byte mBellVolume; + short mBellPitchHZ; + short mBellDurationMS; + int mBellSampleCount; + AudioTrack mBellAudioTrack; + + int mLedMask; + + byte mMinKeycode; + byte mMaxKeycode; + + short mModState; + + short mPointerX; + short mPointerY; + + X11Keyboard() { + mGlobalAutoRepeat = 0; + mKeyClickPercent = 50; + mBellVolume = 50; + mBellPitchHZ = 2000; + mBellDurationMS = 100; + mLedMask = 0x00000000; + mMinKeycode = X_MIN_KEYCODE; + mMaxKeycode = X_MIN_KEYCODE + NUM_KEYCODES - 1; + createBellAudioTrack(); + } + + byte minKeycode() { return mMinKeycode; } + byte maxKeycode() { return mMaxKeycode; } + + void handleQueryPointer(X11Client c, X11RequestMessage msg) throws X11Error { + X11Window w = c.getWindow(msg.mData.deqInt()); + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData((byte)1 /*True*/); + reply.mData.enqInt(c.mServer.mDefaultScreen.mRoot.mId); + reply.mData.enqInt(X11Resource.NONE); //XXX: child + reply.mData.enqShort(mPointerX); + reply.mData.enqShort(mPointerY); + reply.mData.enqShort((short)(mPointerX - w.mRect.x)); // win-x + reply.mData.enqShort((short)(mPointerY - w.mRect.y)); // win-y + reply.mData.enqShort(mModState); + c.send(reply); + } + + void handleGetKeyboardMapping(X11Client c, X11RequestMessage msg) throws X11Error { + int first_keycode = msg.mData.deqByte(); + int count = msg.mData.deqByte(); + Log.d("X", "handleGetKeyboardMapping: fc="+first_keycode+", count="+count); + if (first_keycode < mMinKeycode) { + throw new X11Error(X11Error.VALUE, first_keycode); + } + if (first_keycode+count-1 > mMaxKeycode) { + throw new X11Error(X11Error.VALUE, count); + } + + byte keysyms_per_keycode = 2; + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData(keysyms_per_keycode); + reply.mData.enqSkip(24); + int kc; + for (kc = first_keycode; kc < first_keycode+count; ++kc) { + reply.mData.enqInt(key_codes[kc-X_MIN_KEYCODE][0]); + reply.mData.enqInt(key_codes[kc-X_MIN_KEYCODE][1]); + } + c.send(reply); + } + + void handleBell(X11Client c, X11RequestMessage msg) { + byte volumePercent = msg.headerData(); + int volume; + if (volumePercent >= 0) { + volume = mBellVolume - ((mBellVolume * volumePercent) / 100) + volumePercent; + } + else { + volume = mBellVolume + ((mBellVolume * volumePercent) / 100); + } + int frame_count = mBellSampleCount/2; //XXX ??? + int loop_count = mBellDurationMS * mBellPitchHZ / 1000; + mBellAudioTrack.setStereoVolume(volume, volume); + mBellAudioTrack.setLoopPoints(0, frame_count, loop_count); + mBellAudioTrack.play(); + } + + void handleGetModifierMapping(X11Client c, X11RequestMessage msg) { + byte keycodes_per_modifier = 2; + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData(keycodes_per_modifier); + reply.mData.enqSkip(24); + int m; + for (m = 0; m < NUM_MODIFIERS; ++m) { + reply.mData.enqInt(mod_map[m][0] + X_MIN_KEYCODE); + reply.mData.enqInt(mod_map[m][1] + X_MIN_KEYCODE); + } + c.send(reply); + } + + void onKeyDown(int code) { + int m; + for (m = 0; m < NUM_MODIFIERS; ++m) { + if (mod_map[m][0] == code || mod_map[m][1] == code) { + mModState |= (1 << m); + } + } + } + + void onKeyUp(int code) { + int m; + for (m = 0; m < NUM_MODIFIERS; ++m) { + if (mod_map[m][0] == code || mod_map[m][1] == code) { + mModState &= ~(1 << m); + } + } + } + + void createBellAudioTrack() { + int sample_rate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_NOTIFICATION); + mBellSampleCount = (sample_rate / mBellPitchHZ); + byte[] pcm_data = new byte[2*mBellSampleCount]; + for (int i = 0; i < mBellSampleCount; ++i) { + short val = (short)(32767 * Math.sin(2 * Math.PI * i / mBellSampleCount)); + pcm_data[2*i+0] = (byte)(val & 0xff); + pcm_data[2*i+1] = (byte)(val >> 8); + } + mBellAudioTrack = new AudioTrack( + AudioManager.STREAM_NOTIFICATION, + sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_MONO, + AudioFormat.ENCODING_PCM_16BIT, + mBellSampleCount, + AudioTrack.MODE_STATIC); + mBellAudioTrack.write(pcm_data, 0, mBellSampleCount); + } +} diff --git a/src/tdm/xserver/X11Message.java b/src/tdm/xserver/X11Message.java new file mode 100644 index 0000000..5b7bd38 --- /dev/null +++ b/src/tdm/xserver/X11Message.java @@ -0,0 +1,21 @@ +package tdm.xserver; + +import java.nio.ByteOrder; + +abstract class X11Message +{ + ByteQueue mData; + + abstract void read(ByteQueue q) throws Exception; + abstract void write(ByteQueue q); + abstract void dispatch(X11ProtocolHandler h); + + X11Message(ByteOrder endian) { + mData = new ByteQueue(32-4); + mData.endian(endian); + } + + int QUADLEN(int val) { + return ((val+3)/4); + } +} diff --git a/src/tdm/xserver/X11Pixmap.java b/src/tdm/xserver/X11Pixmap.java new file mode 100644 index 0000000..1aa445d --- /dev/null +++ b/src/tdm/xserver/X11Pixmap.java @@ -0,0 +1,51 @@ +package tdm.xserver; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Path; + +class X11Pixmap extends X11Drawable +{ + static void create(X11Client c, X11RequestMessage msg) throws X11Error { + int id = msg.mData.deqInt(); + X11Pixmap r = new X11Pixmap(id); + r.handleCreate(c, msg); + c.addResource(r); + } + + + X11Drawable mDrawable; + + X11Pixmap(int id) { + super(X11Resource.PIXMAP, id); + mDrawable = null; + } + + void destroy() { + mDrawable = null; + super.destroy(); + } + + void handleCreate(X11Client c, X11RequestMessage msg) throws X11Error { + byte depth = msg.headerData(); + X11Drawable d = c.getDrawable(msg.mData.deqInt()); + short w = msg.mData.deqShort(); + short h = msg.mData.deqShort(); + if (w == 0 || h == 0) { + throw new X11Error(X11Error.VALUE, 0); + } + doCreate(depth, w, h, d); + } + + void handleFreePixmap(X11Client c, X11RequestMessage msg) { + c.delResource(mId); + } + + void doCreate(byte depth, short w, short h, X11Drawable d) { + mDepth = depth; + mRect = new X11Rect((short)0, (short)0, w, h); + mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + mDrawable = d; + } +} diff --git a/src/tdm/xserver/X11Point.java b/src/tdm/xserver/X11Point.java new file mode 100644 index 0000000..37c7aad --- /dev/null +++ b/src/tdm/xserver/X11Point.java @@ -0,0 +1,7 @@ +package tdm.xserver; + +class X11Point +{ + short x; + short y; +} diff --git a/src/tdm/xserver/X11Pointer.java b/src/tdm/xserver/X11Pointer.java new file mode 100644 index 0000000..6a8a0ae --- /dev/null +++ b/src/tdm/xserver/X11Pointer.java @@ -0,0 +1,15 @@ +package tdm.xserver; + +import android.util.Log; + +import java.util.ArrayList; + +class X11Pointer +{ + int mButtonState; + X11Point mLocation; + + X11Pointer() { + mLocation = new X11Point(); + } +} diff --git a/src/tdm/xserver/X11Property.java b/src/tdm/xserver/X11Property.java new file mode 100644 index 0000000..56eae6b --- /dev/null +++ b/src/tdm/xserver/X11Property.java @@ -0,0 +1,30 @@ +package tdm.xserver; + +class X11Property +{ + int mType; + byte mFormat; + byte[] mVal; + + X11Property(int t, byte f) { + mType = t; + mFormat = f; + mVal = null; + } + + void setValue(byte[] buf) { + mVal = buf; + } + void appendValue(byte[] buf) { + byte[] newval = new byte[mVal.length + buf.length]; + System.arraycopy(mVal, 0, newval, 0, mVal.length); + System.arraycopy(buf, 0, newval, mVal.length, buf.length); + mVal = newval; + } + void prependValue(byte[] buf) { + byte[] newval = new byte[mVal.length + buf.length]; + System.arraycopy(buf, 0, newval, 0, buf.length); + System.arraycopy(mVal, 0, newval, buf.length, mVal.length); + mVal = newval; + } +} diff --git a/src/tdm/xserver/X11Protocol.java b/src/tdm/xserver/X11Protocol.java new file mode 100644 index 0000000..bd46172 --- /dev/null +++ b/src/tdm/xserver/X11Protocol.java @@ -0,0 +1,103 @@ +package tdm.xserver; + +import java.net.Socket; +import java.io.InputStream; +import java.io.IOException; +import java.io.EOFException; +import java.net.SocketException; +import java.lang.Exception; +import java.nio.ByteOrder; + +interface X11ProtocolHandler +{ + void onMessage(X11SetupRequest msg); + void onMessage(X11SetupResponse msg); + void onMessage(X11RequestMessage msg); + void onMessage(X11ReplyMessage msg); + void onMessage(X11EventMessage msg); + void onMessage(X11ErrorMessage msg); +} + +class X11Protocol +{ + X11ProtocolHandler mHandler; + Socket mSock; + ByteOrder mEndian; + ByteQueue mQueue; + short mSendSeqNo; + short mRecvSeqNo; + boolean mBigReq; + + X11Protocol(X11ProtocolHandler handler, Socket sock) { + mHandler = handler; + mSock = sock; + + try { + sock.setTcpNoDelay(true); + } + catch (SocketException e) { + // Ignore + } + } + + void close() { + try { + mSock.close(); + } + catch (IOException e) { + // Ignore + } + } + + void send(X11Message msg) throws IOException { + ByteQueue q = new ByteQueue(32); + q.endian(mEndian); + msg.write(q); + mSock.getOutputStream().write(q.getBytes()); + } + + void read() throws IOException { + byte[] buf = new byte[1500]; + int len = mSock.getInputStream().read(buf, 0, buf.length); + if (len < 0) { + throw new EOFException(); + } + if (mQueue == null) { + if (buf[0] == 0x42) { + mEndian = ByteOrder.BIG_ENDIAN; + } + else { + mEndian = ByteOrder.LITTLE_ENDIAN; + } + mQueue = new ByteQueue(32); + mQueue.endian(mEndian); + } + mQueue.pos(mQueue.length()); + mQueue.enqArray(buf, 0, len); + + mQueue.pos(0); + while (mQueue.remain() >= 4) { + X11Message msg = null; + if (mRecvSeqNo == 0) { + msg = new X11SetupRequest(mEndian); + } + else { + msg = new X11RequestMessage(mEndian, mBigReq); + } + + int oldpos = mQueue.pos(); + try { + msg.read(mQueue); + } + catch (Exception e) { + mQueue.pos(oldpos); + break; + } + + msg.dispatch(mHandler); + ++mRecvSeqNo; + } + + mQueue.compact(); + } +} diff --git a/src/tdm/xserver/X11Rect.java b/src/tdm/xserver/X11Rect.java new file mode 100644 index 0000000..0db07c4 --- /dev/null +++ b/src/tdm/xserver/X11Rect.java @@ -0,0 +1,22 @@ +package tdm.xserver; + +class X11Rect +{ + short x; + short y; + short w; + short h; + + X11Rect() { + x = 0; + y = 0; + w = 0; + h = 0; + } + X11Rect(short _x, short _y, short _w, short _h) { + x = _x; + y = _y; + w = _w; + h = _h; + } +} diff --git a/src/tdm/xserver/X11ReplyMessage.java b/src/tdm/xserver/X11ReplyMessage.java new file mode 100644 index 0000000..fa8925c --- /dev/null +++ b/src/tdm/xserver/X11ReplyMessage.java @@ -0,0 +1,39 @@ +package tdm.xserver; + +import java.nio.ByteOrder; + +class X11ReplyMessage extends X11Message +{ + byte mHeaderData; + short mSeqNo; + + X11ReplyMessage(X11RequestMessage msg) { + super(msg.mData.endian()); + mData.resize(28); + } + + void read(ByteQueue q) { + byte event_type = q.deqByte(); // 0x01 + mHeaderData = q.deqByte(); + mSeqNo = q.deqShort(); + int exlen = q.deqInt(); + mData = q.deqData(24 + exlen*4); + } + void write(ByteQueue q) { + q.enqByte((byte)0x01); + q.enqByte(mHeaderData); + q.enqShort(mSeqNo); + + mData.enqAlign(4); + int datalen = Math.max(24, mData.pos()); + q.enqInt((datalen-24)/4); + q.enqData(mData); + q.enqSkip(datalen - mData.pos()); + } + void dispatch(X11ProtocolHandler h) { h.onMessage(this); } + + void headerData(byte val) { mHeaderData = val; } + byte headerData() { return mHeaderData; } + void seqno(short val) { mSeqNo = val; } + short seqno() { return mSeqNo; } +} diff --git a/src/tdm/xserver/X11RequestMessage.java b/src/tdm/xserver/X11RequestMessage.java new file mode 100644 index 0000000..fee795f --- /dev/null +++ b/src/tdm/xserver/X11RequestMessage.java @@ -0,0 +1,195 @@ +package tdm.xserver; + +import android.util.Log; + +import java.nio.ByteOrder; + +class X11RequestMessage extends X11Message +{ + static final String[] message_names = { + "NONE", // 0 + "CreateWindow", + "ChangeWindowAttributes", + "GetWindowAttributes", + "DestroyWindow", + "DestroySubwindows", + "ChangeSaveSet", + "ReparentWindow", + "MapWindow", + "MapSubwindows", + "UnmapWindow", // 10 + "UnmapSubwindows", + "ConfigureWindow", + "CirculateWindow", + "GetGeometry", + "QueryTree", + "InternAtom", + "GetAtomName", + "ChangeProperty", + "DeleteProperty", + "GetProperty", // 20 + "ListProperties", + "SetSelectionOwner", + "GetSelectionOwner", + "ConvertSelection", + "SendEvent", + "GrabPointer", + "UngrabPointer", + "GrabButton", + "UngrabButton", + "ChangeActivePointerGrab", // 30 + "GrabKeyboard", + "UngrabKeyboard", + "GrabKey", + "UngrabKey", + "AllowEvents", + "GrabServer", + "UngrabServer", + "QueryPointer", + "GetMotionEvents", + "TranslateCoords", // 40 + "WarpPointer", + "SetInputFocus", + "GetInputFocus", + "QueryKeymap", + "OpenFont", + "CloseFont", + "QueryFont", + "QueryTextExtents", + "ListFonts", + "ListFontsWithInfo", // 50 + "SetFontPath", + "GetFontPath", + "CreatePixmap", + "FreePixmap", + "CreateGC", + "ChangeGC", + "CopyGC", + "SetDashes", + "SetClipRectangles", + "FreeGC", // 60 + "ClearArea", + "CopyArea", + "CopyPlane", + "PolyPoint", + "PolyLine", + "PolySegment", + "PolyRectangle", + "PolyArc", + "FillPoly", + "PolyFillRectangle", // 70 + "PolyFillArc", + "PutImage", + "GetImage", + "PolyText8", + "PolyText16", + "ImageText8", + "ImageText16", + "CreateColormap", + "FreeColormap", + "CopyColormapAndFree", // 80 + "InstallColormap", + "UninstallColormap", + "ListInstalledColormaps", + "AllocColor", + "AllocNamedColor", + "AllocColorCells", + "AllocColorPlanes", + "FreeColors", + "StoreColors", + "StoreNamedColor", // 90 + "QueryColors", + "LookupColor", + "CreateCursor", + "CreateGlyphCursor", + "FreeCursor", + "RecolorCursor", + "QueryBestSize", + "QueryExtension", + "ListExtensions", + "ChangeKeyboardMapping", // 100 + "GetKeyboardMapping", + "ChangeKeyboardControl", + "GetKeyboardControl", + "Bell", + "ChangePointerControl", + "GetPointerControl", + "SetScreenSaver", + "GetScreenSaver", + "ChangeHosts", + "ListHosts", // 110 + "SetAccessControl", + "SetCloseDownMode", + "KillClient", + "RotateProperties", + "ForceScreenSaver", + "SetPointerMapping", + "GetPointerMapping", + "SetModifierMapping", + "GetModifierMapping", + "120", // 120 + "121", + "122", + "123", + "124", + "125", + "126", + "NoOperation" + }; + + String name() { return message_names[mRequestType]; } + + byte mRequestType; + byte mHeaderData; + boolean mBigReq; + + X11RequestMessage(ByteOrder endian, boolean bigreq) { + super(endian); + mBigReq = bigreq; + } + + void read(ByteQueue q) throws Exception { + mRequestType = q.deqByte(); + mHeaderData = q.deqByte(); + short reqlen = q.deqShort(); + if (reqlen == 0) { + if (!mBigReq) { + throw new Exception("X11 protocol error: invalid message length"); + } + int bigreqlen = q.deqInt(); + if (bigreqlen < 2) { + throw new Exception("X11 protocol error: invalid message length"); + } + mData = q.deqData(bigreqlen*4-8); + } + else { + mData = q.deqData((int)reqlen*4-4); //XXX: cast needed and functional? + } + } + void write(ByteQueue q) { + q.enqByte(mRequestType); + q.enqByte(mHeaderData); + + int reqlen = ((4+mData.length())+3)/4; + if (reqlen > 0xffff) { + if (!mBigReq) { + //XXX: throw new Exception("X11 protocol error: message too big"); + System.exit(-1); + } + reqlen += 1; + q.enqShort((short)0); + q.enqInt(reqlen); + } + else { + q.enqShort((short)reqlen); + } + q.enqData(mData); + q.enqAlign(4); + } + void dispatch(X11ProtocolHandler h) { h.onMessage(this); } + + void requestType(byte val) { mRequestType = val; } + byte requestType() { return mRequestType; } + void headerData(byte val) { mHeaderData = val; } + byte headerData() { return mHeaderData; } +} diff --git a/src/tdm/xserver/X11Resource.java b/src/tdm/xserver/X11Resource.java new file mode 100644 index 0000000..206272c --- /dev/null +++ b/src/tdm/xserver/X11Resource.java @@ -0,0 +1,34 @@ +package tdm.xserver; + +class X11Resource +{ + // Resource constants + static final int MIN_ID = 4; + static final int ID_DEF_COLORMAP = MIN_ID+0; + static final int ID_ROOT_WINDOW = MIN_ID+1; + + static final int NONE = 0; + + static final int NEVER = 0; + + // Resource types + static final int WINDOW = 1; + static final int PIXMAP = 2; + static final int GCONTEXT = 3; + static final int FONT = 4; + static final int CURSOR = 5; + static final int COLORMAP = 6; + static final int CMAPENTRY = 7; + static final int OTHERCLIENT = 8; + static final int PASSIVEGRAB = 9; + + int mType; + int mId; + + X11Resource(int t, int n) { + mType = t; + mId = n; + } + + void destroy() {} +} diff --git a/src/tdm/xserver/X11Screen.java b/src/tdm/xserver/X11Screen.java new file mode 100644 index 0000000..46389c6 --- /dev/null +++ b/src/tdm/xserver/X11Screen.java @@ -0,0 +1,148 @@ +package tdm.xserver; + +import android.util.Log; + +import java.util.ArrayList; + +class X11Screen +{ + X11Window mRoot; + X11Colormap mDefaultColormap; + int mWhitePixel; + int mBlackPixel; + int mCurrentInputMasks; + short mWidthPx; + short mHeightPx; + short mWidthMM; + short mHeightMM; + short mMinInstalledMaps; + short mMaxInstalledMaps; + int mRootVisual; //XXX: property of root? + byte mBackingStores; + byte mSaveUnders; + byte mRootDepth; //XXX: property of root? + ArrayList mAllowedDepths; + // ... + + + X11Screen(X11Server server, X11Client client, short w, short h, short dpi) throws X11Error { + mAllowedDepths = new ArrayList(); + + mDefaultColormap = new X11Colormap(X11Resource.ID_DEF_COLORMAP); + client.addResource(mDefaultColormap); + + mRoot = new X11Window(X11Resource.ID_ROOT_WINDOW); + client.addResource(mRoot); + + mDefaultColormap.initDefault(); + mRoot.mColormap = mDefaultColormap; + + int vid = 0; + X11Depth d; + X11Visual v; + + d = new X11Depth((byte)24); + + v = new X11Visual(++vid, (byte)24); + v.mVisClass = X11Visual.DIRECT_COLOR; + v.mRedMask = (0xff << 16); + v.mGreenMask = (0xff << 8); + v.mBlueMask = (0xff); + v.mBitsPerRgbValue = 8; + v.mColormapEntries = (1 << 8); + server.addVisual(v); + d.visuals.add(v); + + v = new X11Visual(++vid, (byte)24); + v.mVisClass = X11Visual.TRUE_COLOR; + v.mRedMask = (0xff << 16); + v.mGreenMask = (0xff << 8); + v.mBlueMask = (0xff); + v.mBitsPerRgbValue = 8; + v.mColormapEntries = (1 << 8); + server.addVisual(v); + d.visuals.add(v); + + mAllowedDepths.add(d); + + // root is depth 24, visual DirectColor + mRoot.createRoot(client, w, h, d.depth, v); + + //XXX: depth 1, 4, 8, 15, 16? + + d = new X11Depth((byte)32); + + v = new X11Visual(++vid, (byte)32); + v.mVisClass = X11Visual.DIRECT_COLOR; + v.mRedMask = (0xff << 16); + v.mGreenMask = (0xff << 8); + v.mBlueMask = (0xff); + v.mBitsPerRgbValue = 8; + v.mColormapEntries = (1 << 8); + server.addVisual(v); + d.visuals.add(v); + + v = new X11Visual(++vid, (byte)32); + v.mVisClass = X11Visual.TRUE_COLOR; + v.mRedMask = (0xff << 16); + v.mGreenMask = (0xff << 8); + v.mBlueMask = (0xff); + v.mBitsPerRgbValue = 8; + v.mColormapEntries = (1 << 8); + server.addVisual(v); + d.visuals.add(v); + + mAllowedDepths.add(d); + + mWhitePixel = 0x00ffffff; + mBlackPixel = 0; + mCurrentInputMasks = 0; + mWidthPx = w; + mHeightPx = h; + mWidthMM = (short)((254*w)/(10*dpi)); + mHeightMM = (short)((254*h)/(10*dpi)); + mMinInstalledMaps = 1; + mMaxInstalledMaps = 1; + mRootVisual = mRoot.mVisual.mId; + mBackingStores = X11Resource.NEVER; + mSaveUnders = 0; + mRootDepth = mRoot.mDepth; + } + + void enqueue(ByteQueue q) { + q.enqInt(mRoot.mId); + q.enqInt(mDefaultColormap.mId); + q.enqInt(mWhitePixel); + q.enqInt(mBlackPixel); + q.enqInt(mCurrentInputMasks); + q.enqShort(mWidthPx); + q.enqShort(mHeightPx); + q.enqShort(mWidthMM); + q.enqShort(mHeightMM); + q.enqShort(mMinInstalledMaps); + q.enqShort(mMaxInstalledMaps); + q.enqInt(mRoot.mVisual.mId); + q.enqByte(mBackingStores); + q.enqByte(mSaveUnders); + q.enqByte(mRoot.mDepth); + + q.enqByte((byte)mAllowedDepths.size()); + for (X11Depth dep : mAllowedDepths) { + q.enqByte(dep.depth); + q.enqSkip(1); + q.enqShort((short)dep.visuals.size()); + q.enqSkip(4); + + for (X11Visual vis : dep.visuals) { + q.enqInt(vis.mId); + q.enqByte(vis.mVisClass); + q.enqByte(vis.mBitsPerRgbValue); + q.enqShort(vis.mColormapEntries); + q.enqInt(vis.mRedMask); + q.enqInt(vis.mGreenMask); + q.enqInt(vis.mBlueMask); + q.enqSkip(4); + } + } + } +} diff --git a/src/tdm/xserver/X11Selection.java b/src/tdm/xserver/X11Selection.java new file mode 100644 index 0000000..2d83d56 --- /dev/null +++ b/src/tdm/xserver/X11Selection.java @@ -0,0 +1,9 @@ +package tdm.xserver; + +class X11Selection +{ + X11Atom mSelection; + X11Client mOwnerClient; + X11Window mOwnerWindow; + int mLastChangeTime; +} diff --git a/src/tdm/xserver/X11Server.java b/src/tdm/xserver/X11Server.java new file mode 100644 index 0000000..35e228a --- /dev/null +++ b/src/tdm/xserver/X11Server.java @@ -0,0 +1,425 @@ +package tdm.xserver; + +import android.util.Log; + +import android.content.Context; +import android.view.Display; +import android.view.WindowManager; + +import android.util.DisplayMetrics; +import android.view.Display; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import java.net.ServerSocket; + +class X11Server extends Thread +{ + Context mContext; + UIHandler mHandler; + + X11Client[] mClients; + + Map mVisuals; + ArrayList mPixmapFormats; + + Map mAtomsById; + Map mAtomsByName; + int mLastAtomId; + + Map mSelections; + + Map mFonts; + Map mFontAliases; + + boolean mRunning; + long mStartTime; + ServerSocket mSock; + + X11Keyboard mKeyboard; + + X11Screen mDefaultScreen; + X11Window mInputFocus; + + X11Client mGrabClient; + + X11Server(Context ctx, UIHandler handler) { + mContext = ctx; + mHandler = handler; + } + + public void run() { + mClients = new X11Client[X11Client.MAX_CLIENTS]; + mClients[0] = new X11Client(this); + + mVisuals = new HashMap(); + + mPixmapFormats = new ArrayList(); + mPixmapFormats.add(new X11Format_1()); //XXX + mPixmapFormats.add(new X11Format_4()); + mPixmapFormats.add(new X11Format_8()); + mPixmapFormats.add(new X11Format_16()); + mPixmapFormats.add(new X11Format_24()); + mPixmapFormats.add(new X11Format_32()); + + X11Colormap.globalInit(this); + + mAtomsById = new HashMap(); + mAtomsByName = new HashMap(); + mLastAtomId = 0; + X11Atom.globalInit(this); + + mSelections = new HashMap(); + + mFonts = new HashMap(); + mFontAliases = new HashMap(); + FontData.globalInit(this); + + // NB: PixelFormat (see dpy.getPixelFormat()) has bitsPerPixel/bytesPerPixel etc. + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + Display dpy = wm.getDefaultDisplay(); + DisplayMetrics dm = new DisplayMetrics(); + dpy.getMetrics(dm); + + mKeyboard = new X11Keyboard(); + + try { + mDefaultScreen = new X11Screen(this, mClients[0], + (short)dm.widthPixels, + (short)dm.heightPixels, + (short)dm.densityDpi); + } + catch (X11Error e) { + Log.e(XServer.TAG, "Cannot create screen"); + return; + } + + try { + mSock = new ServerSocket(6000); + } + catch (Exception e) { + Log.e(XServer.TAG, "Cannot create server socket"); + return; + } + + mStartTime = System.currentTimeMillis(); + mRunning = true; + while (mRunning) { + try { + for (int i = 1; i < X11Client.MAX_CLIENTS; ++i) { + if (mClients[i] == null) { + X11Client c = new X11Client(this, i, mSock.accept()); + mClients[i] = c; + c.start(); + break; + } + } + } + catch (Exception e) { + Log.e(XServer.TAG, "Exception in run"); + mRunning = false; + continue; + } + } + } + + void onStop() { + mRunning = false; + } + + int currentTime() { + return (int)(System.currentTimeMillis() - mStartTime); + } + + void clientClosed(X11Client c) { + //XXX: locking + if (mGrabClient == c) { + mGrabClient = null; + } + //XXX: delete selections + //XXX: ...??? + for (int i = 1; i < X11Client.MAX_CLIENTS; ++i) { + if (mClients[i] == c) { + c = null; + break; + } + } + } + + void addVisual(X11Visual v) throws X11Error { + if (mVisuals.containsKey(v.mId)) { + throw new X11Error(X11Error.IDCHOICE, v.mId); + } + mVisuals.put(v.mId, v); + } + + void delVisual(int id) { + mVisuals.remove(id); + } + + X11Visual getVisual(int id) { + return mVisuals.get(id); + } + + ArrayList getPixmapFormats() { + return mPixmapFormats; + } + + void registerFont(FontData f) { + mFonts.put(f.mName, f); + } + void registerFontAlias(String alias, String name) { + mFontAliases.put(alias, name); + } + + void handleInternAtom(X11Client c, X11RequestMessage msg) { + byte onlyifexist = msg.headerData(); + short namelen = msg.mData.deqShort(); + msg.mData.deqSkip(2); + String name = msg.mData.deqString(namelen); + + int id = X11Atom.NONE; + boolean created = false; + if (!mAtomsByName.containsKey(name)) { + if (onlyifexist == 0 /* False */) { + id = doInternAtom(name); + created = true; + } + } + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.mData.enqInt(id); + c.send(reply); + } + + void handleSetSelectionOwner(X11Client c, X11RequestMessage msg) throws X11Error { + X11Window owner = c.getWindow(msg.mData.deqInt()); + X11Atom selection_name = mAtomsById.get(msg.mData.deqInt()); + int time = msg.mData.deqInt(); + + X11Selection selection = mSelections.get(selection_name.mId); + if (selection == null) { + selection = new X11Selection(); + selection.mSelection = selection_name; + selection.mOwnerClient = c; + selection.mOwnerWindow = owner; + selection.mLastChangeTime = currentTime(); + mSelections.put(selection_name.mId, selection); + } + + if (time < selection.mLastChangeTime) { + return; + } + + if (selection.mOwnerClient != null && selection.mOwnerClient != c) { + X11EventMessage event = new X11EventMessage(selection.mOwnerClient.mProt.mEndian, X11Event.SELECTION_CLEAR); + event.mData.enqInt(time); + event.mData.enqInt(owner.mId); + event.mData.enqInt(selection_name.mId); + selection.mOwnerClient.send(event); + } + + selection.mLastChangeTime = time; + if (owner == null) { + selection.mOwnerClient = null; + selection.mOwnerWindow = null; + } + else { + selection.mOwnerClient = c; + selection.mOwnerWindow = owner; + } + } + + void handleGetSelectionOwner(X11Client c, X11RequestMessage msg) { + int id = X11Resource.NONE; + X11Selection selection = mSelections.get(msg.mData.deqInt()); + if (selection != null) { + id = selection.mOwnerWindow.mId; + } + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.mData.enqInt(id); + c.send(reply); + } + + void handleGrabServer(X11Client c, X11RequestMessage msg) throws X11Error { + if (mGrabClient != null) { + //XXX: handle recursion? + Log.e("X", "Error: GrabServer while server is grabbed"); + throw new X11Error(X11Error.IMPLEMENTATION, 0); + } + mGrabClient = c; + } + + void handleUngrabServer(X11Client c, X11RequestMessage msg) throws X11Error { + if (mGrabClient != c) { + //XXX: ignore this? + Log.e("X", "Error: UngrabServer while server is not grabbed"); + throw new X11Error(X11Error.IMPLEMENTATION, 0); + } + mGrabClient = null; + } + + void handleSetInputFocus(X11Client c, X11RequestMessage msg) throws X11Error { + //XXX + byte revert_to = msg.headerData(); + X11Window w = c.getWindow(msg.mData.deqInt()); + int timestamp = msg.mData.deqInt(); + mInputFocus = w; + } + + void handleGetInputFocus(X11Client c, X11RequestMessage msg) { + //XXX + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData((byte)0); // revert-to = None + reply.mData.enqInt(mInputFocus.mId); // None + c.send(reply); + } + + void handleOpenFont(X11Client c, X11RequestMessage msg) throws X11Error { + int fid = msg.mData.deqInt(); + short len = msg.mData.deqShort(); + msg.mData.deqSkip(2); + String name = msg.mData.deqString(len); + name = name.toLowerCase(); + + if (mFontAliases.containsKey(name)) { + Log.d("X", "OpenFont: alias: <" + name + "> => <" + mFontAliases.get(name) + ">"); + name = mFontAliases.get(name); + } + + FontData data = null; + for (FontData i : mFonts.values()) { + if (i.match(name)) { + data = i; + break; + } + } + + if (data == null) { + Log.d("X", "OpenFont: cannot find font <" + name + ">: trying fixed instead"); + data = mFonts.get(mFontAliases.get("fixed")); + } + + if (data == null) { + Log.d("X", "OpenFont: cannot find font <" + name + ">"); + throw new X11Error(X11Error.NAME, fid); + } + Log.d("X", "OpenFont: opening " + data.mName); + try { + data.loadMetadata(this); + data.loadGlyphs(this); + } + catch (Exception e) { + Log.e("X", "OpenFont: failed to load metadata"); + e.printStackTrace(); + throw new X11Error(X11Error.IMPLEMENTATION, 0); + } + X11Font font = new X11Font(fid, data); + c.addResource(font); + } + + void handleListFonts(X11Client c, X11RequestMessage msg) { + short maxnames = msg.mData.deqShort(); + short len = msg.mData.deqShort(); + String name = msg.mData.deqString(len); + name = name.toLowerCase(); + + ArrayList v = new ArrayList(); + + for (FontData f : mFonts.values()) { + if (f.match(name)) { + v.add(f.mName); + if (v.size() == maxnames) { + break; + } + } + } + + //XXX: how should this be done? + if (v.size() < maxnames) { + String realname = mFontAliases.get(name); + if (realname != null) { + v.add(realname); //XXX? + } + } + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.mData.enqShort((short)v.size()); + reply.mData.enqSkip(22); + + for (String s : v) { + reply.mData.enqByte((byte)s.length()); + reply.mData.enqString(s); + } + reply.mData.enqAlign(4); + c.send(reply); + } + + void handleListFontsWithInfo(X11Client c, X11RequestMessage msg) { + short maxnames = msg.mData.deqShort(); + short len = msg.mData.deqShort(); + String pattern = msg.mData.deqString(len); + pattern = pattern.toLowerCase(); + + ArrayList v = new ArrayList(); + + for (FontData f : mFonts.values()) { + if (f.match(pattern)) { + try { + f.loadMetadata(this); + v.add(f); + if (v.size() == maxnames) { + break; + } + } + catch (Exception e) { + Log.e("X", "Failed to read font " + f.mName); + } + } + } + + X11ReplyMessage reply = new X11ReplyMessage(msg); + + for (int n = 0; n < v.size(); ++n) { + FontData f = v.get(n); + reply.mData.clear(); + f.enqueueInfo(reply.mData); + reply.mData.enqInt(v.size() - n); + f.enqueueProperties(reply.mData); + reply.mData.enqString(f.mName); + reply.headerData((byte)f.mName.length()); + c.send(reply); + } + + reply.mData.clear(); + reply.mData.enqSkip(52); + reply.headerData((byte)0); + c.send(reply); + } + + void handleQueryExtension(X11Client c, X11RequestMessage msg) { + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.mData.enqByte((byte)0); // present + reply.mData.enqByte((byte)0); // major-opcode + reply.mData.enqByte((byte)0); // first-event + reply.mData.enqByte((byte)0); // first-error + c.send(reply); + } + + void doInternAtom(int id, String name) { + X11Atom atom = new X11Atom(id, name); + mAtomsById.put(id, atom); + mAtomsByName.put(name, atom); + } + + int doInternAtom(String name) { + int id = ++mLastAtomId; + doInternAtom(id, name); + return id; + } +} diff --git a/src/tdm/xserver/X11SetupRequest.java b/src/tdm/xserver/X11SetupRequest.java new file mode 100644 index 0000000..35d5c7a --- /dev/null +++ b/src/tdm/xserver/X11SetupRequest.java @@ -0,0 +1,47 @@ +package tdm.xserver; + +import java.nio.ByteOrder; + +class X11SetupRequest extends X11Message //XXX: extends X11Consumer? +{ + ByteOrder mByteOrder; + short mProtoMajor; + short mProtoMinor; + String mAuthProtoName; + byte[] mAuthProtoData; + + X11SetupRequest(ByteOrder endian) { + super(endian); + } + + void read(ByteQueue q) throws Exception { + short name_len, data_len; + + mByteOrder = (q.deqByte() == (byte)0x42) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; + q.deqSkip(1); + mProtoMajor = q.deqShort(); + mProtoMinor = q.deqShort(); + name_len = q.deqShort(); + data_len = q.deqShort(); + q.deqSkip(2); + mAuthProtoName = q.deqString(name_len); + q.deqAlign(4); + mAuthProtoData = q.deqArray(data_len); + q.deqAlign(4); + } + void write(ByteQueue q) { + short name_len = (short)mAuthProtoName.length(); + short data_len = (short)mAuthProtoData.length; + + q.enqByte((mByteOrder == ByteOrder.BIG_ENDIAN) ? (byte)0x42 : (byte)0x6c); + q.enqSkip(1); + q.enqShort(mProtoMajor); + q.enqShort(mProtoMinor); + q.enqShort(name_len); + q.enqShort(data_len); + q.enqSkip(2); + q.enqString(mAuthProtoName); + q.enqArray(mAuthProtoData); + } + void dispatch(X11ProtocolHandler h) { h.onMessage(this); } +} diff --git a/src/tdm/xserver/X11SetupResponse.java b/src/tdm/xserver/X11SetupResponse.java new file mode 100644 index 0000000..0844178 --- /dev/null +++ b/src/tdm/xserver/X11SetupResponse.java @@ -0,0 +1,72 @@ +package tdm.xserver; + +import java.nio.ByteOrder; + +class X11SetupResponse extends X11Message //XXX: extends X11Consumer? +{ + byte mSuccess; + short mProtoMajor; + short mProtoMinor; + String mReason; + + X11SetupResponse(X11SetupRequest msg) { + super(msg.mData.endian()); + } + + void read(ByteQueue q) throws Exception { + mSuccess = q.deqByte(); + if (mSuccess != 1 /* Success */) { + if (mSuccess == 2 /* Authenticate */) { + short add_len; + q.deqSkip(2); + add_len = q.deqShort(); + mReason = q.deqString(add_len*4); + } + else { /* Failed */ + short add_len; + byte reason_len; + reason_len = q.deqByte(); + mProtoMajor = q.deqShort(); + mProtoMinor = q.deqShort(); + add_len = q.deqShort(); + mReason = q.deqString(reason_len); + } + return; + } + + q.deqSkip(1); + mProtoMajor = q.deqShort(); + mProtoMinor = q.deqShort(); + short add_len = q.deqShort(); + mData = q.deqData(add_len*4); + } + void write(ByteQueue q) { + q.enqByte(mSuccess); + if (mSuccess != 1 /* Success */) { + if (mSuccess == 2 /* Authenticate */) { + short add_len = (short)MathX.divceil(mReason.length(), 4); + q.enqSkip(5); + q.enqShort(add_len); + q.enqString(mReason); + } + else { /* Failed */ + short add_len = (short)MathX.divceil(mReason.length(), 4); + byte reason_len = (byte)mReason.length(); + q.enqByte(reason_len); + q.enqShort(mProtoMajor); + q.enqShort(mProtoMinor); + q.enqShort(add_len); + q.enqString(mReason); + } + return; + } + + q.enqSkip(1); + q.enqShort(mProtoMajor); + q.enqShort(mProtoMinor); + short add_len = (short)MathX.divceil(mData.pos(), 4); + q.enqShort(add_len); + q.enqData(mData); + } + void dispatch(X11ProtocolHandler h) { h.onMessage(this); } +} diff --git a/src/tdm/xserver/X11Visual.java b/src/tdm/xserver/X11Visual.java new file mode 100644 index 0000000..cb11860 --- /dev/null +++ b/src/tdm/xserver/X11Visual.java @@ -0,0 +1,28 @@ +package tdm.xserver; + +class X11Visual +{ + static final int NONE = 0; + + static final byte STATIC_GRAY = 0; + static final byte GRAYSCALE = 1; + static final byte STATIC_COLOR = 2; + static final byte PSEUDO_COLOR = 3; + static final byte TRUE_COLOR = 4; + static final byte DIRECT_COLOR = 5; + + int mId; + byte mDepth; + + byte mVisClass; + byte mBitsPerRgbValue; + short mColormapEntries; + int mRedMask; + int mGreenMask; + int mBlueMask; + + X11Visual(int n, byte d) { + mId = n; + mDepth = d; + } +} diff --git a/src/tdm/xserver/X11Window.java b/src/tdm/xserver/X11Window.java new file mode 100644 index 0000000..3cea07f --- /dev/null +++ b/src/tdm/xserver/X11Window.java @@ -0,0 +1,790 @@ +package tdm.xserver; + +import android.util.Log; + +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.Color; +import android.graphics.Canvas; +import android.os.Message; +import android.view.View; +import android.view.ViewGroup; +import android.content.Context; + +import java.lang.Thread; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +class X11Window extends X11Drawable +{ + static final int EVENT_EXPOSURE = 0x00008000; + + static final short COPY_FROM_PARENT = 0; + static final short INPUT_OUTPUT = 1; + static final short INPUT_ONLY = 2; + + static void create(X11Client c, X11RequestMessage msg) throws X11Error { + int id = msg.mData.deqInt(); + X11Window r = new X11Window(id); + r.handleCreate(c, msg); + c.addResource(r); + } + + X11Window mParent; + short mWndClass; + X11Pixmap mBgPixmap; + int mBgPixel; + X11Pixmap mBorderPixmap; + int mBorderPixel; + byte mBitGravity; + byte mWinGravity; + byte mBackingStore; + int mBackingPlanes; + int mBackingPixel; + byte mOverrideRedirect; + byte mSaveUnder; + int mEventMask; + short mDoNotPropagateMask; + X11Colormap mColormap; + X11Cursor mCursor; + + Map mProperties; + + // sibling position + ArrayList mChildren; + + UIHandler mHandler; + ClientView mView; + + boolean mMapped; + boolean mRealized; + + X11Window(int id) { + super(X11Resource.WINDOW, id); + mProperties = new HashMap(); + mChildren = new ArrayList(); + } + + void destroy() { + if (mView != null) { + sendViewMessage(UIHandler.MSG_VIEW_REMOVE); + while (mView != null) { Thread.yield(); } + } + mChildren = null; + mProperties = null; + mCursor = null; + mColormap = null; + mBorderPixmap = null; + mBgPixmap = null; + mParent = null; + super.destroy(); + } + + void createRoot(X11Client c, short w, short h, byte d, X11Visual v) { + mDepth = d; + mRect = new X11Rect((short)0, (short)0, w, h); + mBitmap = Bitmap.createBitmap(mRect.w, mRect.h, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + mBorderWidth = 0; + mWndClass = INPUT_OUTPUT; + mVisual = v; + + mBgPixmap = new X11Pixmap(0); //XXX: should use a real (global) id + mBgPixmap.doCreate(mDepth, (short)3, (short)3, this); + mBgPixmap.mBitmap.setPixel((short)0, (short)0, Color.WHITE); + mBgPixmap.mBitmap.setPixel((short)1, (short)0, Color.BLACK); + mBgPixmap.mBitmap.setPixel((short)2, (short)0, Color.WHITE); + mBgPixmap.mBitmap.setPixel((short)0, (short)1, Color.BLACK); + mBgPixmap.mBitmap.setPixel((short)1, (short)1, Color.WHITE); + mBgPixmap.mBitmap.setPixel((short)2, (short)1, Color.BLACK); + mBgPixmap.mBitmap.setPixel((short)0, (short)2, Color.WHITE); + mBgPixmap.mBitmap.setPixel((short)1, (short)2, Color.BLACK); + mBgPixmap.mBitmap.setPixel((short)2, (short)2, Color.WHITE); + + mHandler = c.mServer.mHandler; + + mMapped = true; + mRealized = true; + + paintBackgroundArea(mRect); + + Log.d("X", "Creating root ClientView"); + sendViewMessage(UIHandler.MSG_VIEW_CREATE_ROOT); + while (mView == null) { Thread.yield(); } + mView.mClient = c; + c.mServer.mInputFocus = this; + } + + void handleCreate(X11Client c, X11RequestMessage msg) throws X11Error { + mDepth = msg.headerData(); + mParent = c.getWindow(msg.mData.deqInt()); + mParent.mChildren.add(this); + + mRect = new X11Rect(); + mRect.x = msg.mData.deqShort(); + mRect.y = msg.mData.deqShort(); + mRect.w = msg.mData.deqShort(); + mRect.h = msg.mData.deqShort(); + mBorderWidth = msg.mData.deqShort(); + + mBitmap = Bitmap.createBitmap(mRect.w, mRect.h, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + + mWndClass = msg.mData.deqShort(); + int vid = msg.mData.deqInt(); + if (mWndClass == COPY_FROM_PARENT) { + mWndClass = mParent.mWndClass; + } + if (mWndClass == INPUT_ONLY) { + if (mDepth != 0) { + throw new X11Error(X11Error.MATCH, mDepth); + } + if (mBorderWidth != 0) { + throw new X11Error(X11Error.MATCH, mBorderWidth); + } + } + else if (mWndClass == INPUT_OUTPUT) { + if (mDepth == 0) { + mDepth = mParent.mDepth; + } + } + else { + throw new X11Error(X11Error.MATCH, mWndClass); + } + + if (vid == 0 /* CopyFromParent */) { + mVisual = mParent.mVisual; + } + else { + mVisual = c.getVisual(vid); + } + if (mRect.w == 0 && mRect.h == 0) { + throw new X11Error(X11Error.VALUE, 0); + } + + //XXX: Add checks per spec here? + + mBorderPixmap = mParent.mBorderPixmap; + mColormap = mParent.mColormap; + + doChangeAttributes(c, msg.mData); + + mHandler = c.mServer.mHandler; + + sendViewMessage(UIHandler.MSG_VIEW_CREATE); + while (mView == null) { Thread.yield(); } + mView.mClient = c; + + paintBackgroundArea(mRect); + + //XXX: xorg server does not seem to send a CreateNotify event? + + } + + void handleChangeWindowAttributes(X11Client c, X11RequestMessage msg) throws X11Error { + if (mParent == null) { + return; + } + + doChangeAttributes(c, msg.mData); + } + + void handleGetWindowAttributes(X11Client c, X11RequestMessage msg) { + byte map_state = (byte)0 /* Unmapped */; + if (mMapped) { + map_state = 1 /* Unviewable */; + if (mRealized) { + map_state = 2 /* Viewable */; + } + } + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData(mBackingStore); + reply.mData.enqInt(mVisual.mId); + reply.mData.enqShort(mWndClass); + reply.mData.enqByte(mBitGravity); + reply.mData.enqByte(mWinGravity); + reply.mData.enqInt(mBackingPlanes); + reply.mData.enqInt(mBackingPixel); + reply.mData.enqByte(mSaveUnder); + reply.mData.enqByte((byte)1 /* True */); //XXX: map-is-installed + reply.mData.enqByte(map_state); + reply.mData.enqByte(mOverrideRedirect); + reply.mData.enqInt(mColormap.mId); + reply.mData.enqInt(mEventMask); + reply.mData.enqInt(mEventMask); + reply.mData.enqShort(mDoNotPropagateMask); + reply.mData.enqSkip(2); + c.send(reply); + } + + void handleDestroyWindow(X11Client c, X11RequestMessage msg) { + doDestroy(c); + } + + void handleDestroySubwindows(X11Client c, X11RequestMessage msg) { + doDestroySub(c); + } + + void handleMapWindow(X11Client c, X11RequestMessage msg) throws X11Error { + doMap(c); + } + + void handleMapSubwindows(X11Client c, X11RequestMessage msg) throws X11Error { + doMapSub(c); + } + + void handleUnmapWindow(X11Client c, X11RequestMessage msg) throws X11Error { + doUnmap(c); + } + + void handleUnmapSubwindows(X11Client c, X11RequestMessage msg) throws X11Error { + doUnmapSub(c); + } + + void handleConfigureWindow(X11Client c, X11RequestMessage msg) throws X11Error { + boolean size_changed = false; + boolean position_changed = false; + short mask = msg.mData.deqShort(); + if ((mask & 0x0001) != 0) { // x + mRect.x = (short)(msg.mData.deqInt() & 0xffff); + position_changed = true; + } + if ((mask & 0x0002) != 0) { // y + mRect.y = (short)(msg.mData.deqInt() & 0xffff); + position_changed = true; + } + if ((mask & 0x0004) != 0) { // width + mRect.w = (short)(msg.mData.deqInt() & 0xffff); + size_changed = true; + } + if ((mask & 0x0008) != 0) { // height + size_changed = true; + mRect.h = (short)(msg.mData.deqInt() & 0xffff); + } + if ((mask & 0x0010) != 0) { // border-width + mBorderWidth = (short)(msg.mData.deqInt() & 0xffff); + } + if ((mask & 0x0020) != 0) { // sibling + //XXX + c.getWindow(msg.mData.deqInt()); + } + if ((mask & 0x0040) != 0) { // stack-mode + //XXX + msg.mData.deqInt(); + } + + if (size_changed) { + mBitmap = Bitmap.createBitmap(mRect.w, mRect.h, Bitmap.Config.ARGB_8888); + mCanvas.setBitmap(mBitmap); + paintBackgroundArea(mRect); + } + if (size_changed || position_changed) { + sendViewMessage(UIHandler.MSG_VIEW_CONFIGURE); + } + } + + void handleChangeProperty(X11Client c, X11RequestMessage msg) throws X11Error { + byte mode = msg.headerData(); // Replace, Prepend, Append + int name = msg.mData.deqInt(); + int type = msg.mData.deqInt(); + byte fmt = msg.mData.deqByte(); + msg.mData.deqSkip(3); + if (fmt != 8 && fmt != 16 && fmt != 32) { + throw new X11Error(X11Error.VALUE, fmt); + } + int datalen = msg.mData.deqInt(); + ByteQueue data = new ByteQueue(); //XXX endian? + while (datalen-- != 0) { + switch (fmt) { + case 8: data.enqByte(msg.mData.deqByte()); break; + case 16: data.enqShort(msg.mData.deqShort()); break; + case 32: data.enqInt(msg.mData.deqInt()); break; + } + } + + //XXX: lots of checks are missing here + + X11Property prop = mProperties.get(name); + if (prop == null) { + prop = new X11Property(type, fmt); + mProperties.put(name, prop); + } + + switch (mode) { + case 0: prop.setValue(data.getBytes()); break; + case 1: prop.appendValue(data.getBytes()); break; + case 2: prop.prependValue(data.getBytes()); break; + } + + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.PROPERTY_NOTIFY); + evt.mData.enqInt(mId); + evt.mData.enqInt(name); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqByte((byte)0); // NewValue + c.send(evt); + } + + void handleDeleteProperty(X11Client c, X11RequestMessage msg) throws X11Error { + int name = msg.mData.deqInt(); + if (mProperties.containsKey(name)) { + mProperties.remove(name); + + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.PROPERTY_NOTIFY); + evt.mData.enqInt(mId); + evt.mData.enqInt(name); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqByte((byte)1); // Deleted + c.send(evt); + } + } + + void handleGetProperty(X11Client c, X11RequestMessage msg) throws X11Error { + byte del = msg.headerData(); + int name = msg.mData.deqInt(); + int type = msg.mData.deqInt(); + int off = msg.mData.deqInt(); + int len = msg.mData.deqInt(); + + X11ReplyMessage reply = new X11ReplyMessage(msg); + + X11Property prop = mProperties.get(name); + if (prop != null) { + int rpos = off * 4; + if (rpos > prop.mVal.length) { + throw new X11Error(X11Error.VALUE, off); + } + int rlen = Math.min(prop.mVal.length - rpos, len*4); + byte[] rval = new byte[rlen]; + System.arraycopy(prop.mVal, rpos, rval, 0, rlen); //XXX: endian? + if (type != 0 /* AnyPropertyType */ && type != prop.mType) { + rpos = 0; + rlen = 0; + del = 0 /* False */; + } + else { + if (prop.mVal.length < (rpos+rlen)) { + del = 0 /* False */; + } + } + + reply.headerData(prop.mFormat); + reply.mData.enqInt(prop.mType); + reply.mData.enqInt(prop.mVal.length - (rpos + rlen)); + reply.mData.enqInt(rval.length / (prop.mFormat/8)); + reply.mData.enqSkip(12); + reply.mData.enqArray(rval); + + if (del != 0 /* False */) { + mProperties.remove(name); + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.PROPERTY_NOTIFY); + evt.mData.enqInt(mId); + evt.mData.enqInt(name); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqByte((byte)1); // Deleted + c.send(evt); + } + } + else { + reply.mData.enqInt(0); // None + reply.mData.enqInt(0); + reply.mData.enqInt(0); + } + c.send(reply); + } + + void handleListProperties(X11Client c, X11RequestMessage msg) throws X11Error { + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.mData.enqShort((short)mProperties.size()); + reply.mData.enqSkip(22); + for (Integer name : mProperties.keySet()) { + reply.mData.enqInt(name); + } + c.send(reply); + } + + void handleTranslateCoordinates(X11Client c, X11RequestMessage msg) throws X11Error { + X11Window dst = c.getWindow(msg.mData.deqInt()); + short src_x = msg.mData.deqShort(); + short src_y = msg.mData.deqShort(); + + X11Point src_origin = absolutePosition(c); + X11Point dst_origin = dst.absolutePosition(c); + + short dst_x = (short)(src_origin.x + src_x - dst_origin.x); + short dst_y = (short)(src_origin.y + src_y - dst_origin.y); + + int childid = 0 /* None */; + for (X11Window child : mChildren) { + if (src_x >= child.mRect.x && src_y >= child.mRect.y && + src_x < child.mRect.x + child.mRect.w && + src_y < child.mRect.y + child.mRect.h) { + childid = child.mId; + break; //XXX: which child(ren) to return here? + } + } + + X11ReplyMessage reply = new X11ReplyMessage(msg); + reply.headerData((byte)1 /* True */); // same-screen + reply.mData.enqInt(childid); // WINDOW or None + reply.mData.enqShort(dst_x); + reply.mData.enqShort(dst_y); + c.send(reply); + } + + void handleClearArea(X11Client c, X11RequestMessage msg) throws X11Error { + if (mWndClass == INPUT_ONLY) { + throw new X11Error(X11Error.MATCH, mId); + } + byte exposures = msg.headerData(); + X11Rect r = new X11Rect(); + r.x = msg.mData.deqShort(); + r.y = msg.mData.deqShort(); + r.w = msg.mData.deqShort(); + if (r.w == 0) { + r.w = (short)(mRect.x - r.x); + } + r.h = msg.mData.deqShort(); + if (r.h == 0) { + r.h = (short)(mRect.y - r.y); + } + paintBackgroundArea(r); + mView.postInvalidate(r.x, r.y, r.x+r.w, r.y+r.h); + if (exposures != 0 /* False */) { //XXX: independent of EXPOSURES event mask? + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.EXPOSE); + evt.mData.enqInt(mId); + evt.mData.enqShort((short)0); // x + evt.mData.enqShort((short)0); // y + evt.mData.enqShort(mRect.w); // width + evt.mData.enqShort(mRect.h); // height + evt.mData.enqShort((short)0); // count + c.send(evt); // XXX: thread sync? + } + } + + void postRender(X11Rect r) { + mView.postInvalidate(r.x, r.y, r.x+r.w, r.y+r.h); + } + + void postRender() { + mView.postInvalidate(); + } + + X11Point absolutePosition(X11Client c) throws X11Error { + X11Point pos = new X11Point(); + if (mParent != null) { + pos = c.getWindow(mParent.mId).absolutePosition(c); + } + pos.x += mRect.x; + pos.y += mRect.y; + return pos; + } + + private void doChangeAttributes(X11Client c, ByteQueue q) throws X11Error { + int mask = q.deqInt(); + int val; + + if (mWndClass == INPUT_ONLY) { + if ((mask & 0x25df) != 0) { + throw new X11Error(X11Error.MATCH, mask); + } + } + + if ((mask & 0x0001) != 0) { // background-pixmap + val = q.deqInt(); + if (val == 0 /* None */) { + mBgPixmap = null; + } + else if (val == 1 /* ParentRelative */) { + //XXX + mBgPixmap = null; + } + else { + mBgPixmap = c.getPixmap(val); + } + paintBackgroundArea(mRect); + } + if ((mask & 0x0002) != 0) { // background-pixel + mBgPixel = (q.deqInt() | 0xff000000); + paintBackgroundArea(mRect); + } + if ((mask & 0x0004) != 0) { // border-pixmap + mBorderPixmap = c.getPixmap(q.deqInt()); + } + if ((mask & 0x0008) != 0) { // border-pixel + mBorderPixel = (q.deqInt() | 0xff000000); + } + if ((mask & 0x0010) != 0) { // bit-gravity + mBitGravity = (byte)q.deqInt(); + } + if ((mask & 0x0020) != 0) { // win-gravity + mWinGravity = (byte)q.deqInt(); + } + if ((mask & 0x0040) != 0) { // backing-store + mBackingStore = (byte)q.deqInt(); + } + if ((mask & 0x0080) != 0) { // backing-planes + mBackingPlanes = q.deqInt(); + } + if ((mask & 0x0100) != 0) { // backing-pixel + mBackingPixel = (q.deqInt() | 0xff000000); + } + if ((mask & 0x0200) != 0) { // override-redirect + mOverrideRedirect = (byte)q.deqInt(); + } + if ((mask & 0x0400) != 0) { // save-under + mSaveUnder = (byte)q.deqInt(); + } + if ((mask & 0x0800) != 0) { // event-mask + mEventMask = q.deqInt(); + } + if ((mask & 0x1000) != 0) { // do-not-propagate-mask + mDoNotPropagateMask = (short)(q.deqInt() & ~0xffffc0b0); + } + if ((mask & 0x2000) != 0) { // colormap + //XXX: 0 = CopyFromParent + mColormap = c.getColormap(q.deqInt()); + } + if ((mask & 0x4000) != 0) { // cursor + mCursor = c.getCursor(q.deqInt()); + } + } + + private void doDestroy(X11Client c) { + if (mParent == null) { + return; + } + for (X11Window child : mChildren) { + child.doDestroy(c); + } + doUnmap(c); + + sendViewMessage(UIHandler.MSG_VIEW_REMOVE); + mCursor = null; + mColormap = null; + mBorderPixmap = null; + mBgPixmap = null; + mVisual = null; + mCanvas = null; + mBitmap = null; + mRect = null; + mParent.mChildren.remove(this); + mParent= null; + + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.DESTROY_NOTIFY); + evt.mData.enqInt(mId); // event + evt.mData.enqInt(mId); // window + c.send(evt); + } + + private void doDestroySub(X11Client c) { + //XXX: bottom-to-top stacking order + for (X11Window child : mChildren) { + child.doDestroy(c); + } + } + + private void doMap(X11Client c) { + if (mMapped) { + return; + } + //XXX: check override-redirect + mMapped = true; + if (mParent == null) { + mRealized = true; + } + else { + if (mParent.mRealized) { + doRealizeSub(c); + doRealize(c); + } + } + + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.MAP_NOTIFY); + evt.mData.enqInt(mId); // event + evt.mData.enqInt(mId); // window + evt.mData.enqByte(mOverrideRedirect); + c.send(evt); + } + + private void doMapSub(X11Client c) { + for (X11Window child : mChildren) { + child.doMapSub(c); + child.doMap(c); + } + } + + private void doUnmap(X11Client c) { + if (!mMapped) { + return; + } + if (mParent == null) { + return; + } + + mMapped = false; + + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.UNMAP_NOTIFY); + evt.mData.enqInt(mId); // event + evt.mData.enqInt(mId); // window + evt.mData.enqByte((byte)0); // from-configure + c.send(evt); + } + + private void doUnmapSub(X11Client c) { + for (X11Window child : mChildren) { + child.doUnmap(c); + child.doUnmapSub(c); + } + } + + private void doRealizeSub(X11Client c) { + for (X11Window child : mChildren) { + child.doRealizeSub(c); + child.doRealize(c); + } + } + + private void doRealize(X11Client c) { + Log.d("X", "Window#"+mId+".doRealize: set visible"); + sendViewMessage(UIHandler.MSG_VIEW_SET_VISIBLE); + while (!mRealized) { Thread.yield(); } + + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.EXPOSE); + evt.mData.enqInt(mId); + evt.mData.enqShort((short)0); // x + evt.mData.enqShort((short)0); // y + evt.mData.enqShort(mRect.w); // width + evt.mData.enqShort(mRect.h); // height + evt.mData.enqShort((short)0); // count + c.send(evt); // XXX: thread sync? + } + + void onKeyPress(X11Client c, int code) { + if ((mEventMask & X11Event.KEY_PRESS) == 0) { + return; //XXX ??? + } + X11Window root = c.mServer.mDefaultScreen.mRoot; + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.KEY_PRESS); + evt.headerData((byte)(X11Keyboard.X_MIN_KEYCODE+code)); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqInt(root.mId); + evt.mData.enqInt(mId); + evt.mData.enqInt(0 /*None*/); + evt.mData.enqShort((short)0); // root-x + evt.mData.enqShort((short)0); // root-y + evt.mData.enqShort((short)0); // event-x + evt.mData.enqShort((short)0); // event-y + evt.mData.enqShort(c.mServer.mKeyboard.mModState); + evt.mData.enqByte((byte)1); // same-screen = True + evt.mData.enqSkip(1); + c.send(evt); + } + + void onKeyRelease(X11Client c, int code) { + if ((mEventMask & X11Event.KEY_RELEASE) == 0) { + return; //XXX ??? + } + X11Window root = c.mServer.mDefaultScreen.mRoot; + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.KEY_RELEASE); + evt.headerData((byte)(X11Keyboard.X_MIN_KEYCODE+code)); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqInt(root.mId); + evt.mData.enqInt(mId); + evt.mData.enqInt(0 /*None*/); + evt.mData.enqShort((short)0); // root-x + evt.mData.enqShort((short)0); // root-y + evt.mData.enqShort((short)0); // event-x + evt.mData.enqShort((short)0); // event-y + evt.mData.enqShort(c.mServer.mKeyboard.mModState); // state + evt.mData.enqByte((byte)1); // same-screen = True + evt.mData.enqSkip(1); + c.send(evt); + } + + void onButtonPress(X11Client c, int x, int y, int num) { + if ((mEventMask & X11Event.BUTTON_PRESS) == 0) { + return; //XXX ??? + } + X11Window root = c.mServer.mDefaultScreen.mRoot; + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.BUTTON_PRESS); + evt.headerData((byte)num); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqInt(root.mId); + evt.mData.enqInt(mId); + evt.mData.enqInt(0 /*None*/); + evt.mData.enqShort((short)(mRect.x+x)); // root-x + evt.mData.enqShort((short)(mRect.y+y)); // root-y + evt.mData.enqShort((short)x); // event-x + evt.mData.enqShort((short)y); // event-y + evt.mData.enqShort((short)0); // state + evt.mData.enqByte((byte)1); // same-screen = True + evt.mData.enqSkip(1); + c.send(evt); + } + + void onButtonRelease(X11Client c, int x, int y, int num) { + if ((mEventMask & X11Event.BUTTON_RELEASE) == 0) { + return; //XXX ??? + } + X11Window root = c.mServer.mDefaultScreen.mRoot; + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.BUTTON_RELEASE); + evt.headerData((byte)num); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqInt(root.mId); + evt.mData.enqInt(mId); + evt.mData.enqInt(0 /*None*/); + evt.mData.enqShort((short)(mRect.x+x)); // root-x + evt.mData.enqShort((short)(mRect.y+y)); // root-y + evt.mData.enqShort((short)x); // event-x + evt.mData.enqShort((short)y); // event-y + evt.mData.enqShort((short)0); // state + evt.mData.enqByte((byte)1); // same-screen = True + evt.mData.enqSkip(1); + c.send(evt); + } + + void onMotion(X11Client c, int x, int y) { + if ((mEventMask & X11Event.MOTION_NOTIFY) == 0) { + return; //XXX ??? + } + X11Window root = c.mServer.mDefaultScreen.mRoot; + X11EventMessage evt = new X11EventMessage(c.mProt.mEndian, X11Event.MOTION_NOTIFY); + evt.headerData((byte)0 /*Normal*/); + evt.mData.enqInt(c.mServer.currentTime()); + evt.mData.enqInt(root.mId); + evt.mData.enqInt(mId); + evt.mData.enqInt(0 /*None*/); + evt.mData.enqShort((short)(mRect.x+x)); // root-x + evt.mData.enqShort((short)(mRect.y+y)); // root-y + evt.mData.enqShort((short)x); // event-x + evt.mData.enqShort((short)y); // event-y + evt.mData.enqShort((short)0); // state + evt.mData.enqByte((byte)1); // same-screen = True + evt.mData.enqSkip(1); + c.send(evt); + } + + private void paintBackgroundArea(X11Rect r) { + mCanvas.save(Canvas.CLIP_SAVE_FLAG); + mCanvas.clipRect(r.x, r.y, r.x+r.w, r.y+r.h); + if (mBgPixel != 0) { //XXX: need a flag, 0 is a valid value + mCanvas.drawColor(0xff000000 | (mBgPixel & 0x00ffffff)); + } + else if (mBgPixmap != null) { + for (int x = 0; x < mRect.w; x += mBgPixmap.mRect.w) { + for (int y = 0; y < mRect.h; y += mBgPixmap.mRect.h) { + mCanvas.drawBitmap(mBgPixmap.mBitmap, x, y, null); + } + } + } + mCanvas.restore(); + } + + private void sendViewMessage(int func) { + Message msg = Message.obtain(mHandler, func, this); + mHandler.sendMessage(msg); + } +} diff --git a/src/tdm/xserver/XServer.java b/src/tdm/xserver/XServer.java new file mode 100644 index 0000000..7a4392b --- /dev/null +++ b/src/tdm/xserver/XServer.java @@ -0,0 +1,94 @@ +package tdm.xserver; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuInflater; +import android.view.View; + +import android.widget.RelativeLayout; + +import java.net.ServerSocket; + +public class XServer extends Activity +{ + static final String TAG = "XServer"; + + static XServer mInstance; + + RelativeLayout mLayout; + X11Server mServer; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mInstance = this; + } + + protected void onResume() { + Log.e(TAG, "onResume"); + super.onResume(); + + try { + mLayout = new RelativeLayout(this); + setContentView(mLayout); + + UIHandler handler = new UIHandler(this, mLayout); + + mServer = new X11Server(this, handler); + mServer.start(); + } + catch (Exception e) { + Log.e(TAG, "Cannot create server", e); + finish(); + return; + } + } + + protected void onSaveInstanceState(Bundle outState) { + // ...? + } + + protected void onPause() { + Log.e(TAG, "onPause"); + super.onPause(); + // ...? + } + + protected void onStop() { + Log.e(TAG, "onStop"); + super.onStop(); + mServer.onStop(); + // ...? + } + + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.options_menu, menu); + + // Other manipulations... + + return super.onCreateOptionsMenu(menu); + } + + public boolean onPrepareOptionsMenu(Menu menu) { + // Other manipulations... + + return super.onPrepareOptionsMenu(menu); + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_save: + // ... + break; + case R.id.menu_delete: + // ... + break; + } + return super.onOptionsItemSelected(item); + } +}