commit b20498caa4d6312339026e0b9881b5409978d4c5 Author: Tom Marshall Date: Fri Dec 23 15:28:12 2011 -0800 Initial import 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 0000000..bc153fa Binary files /dev/null and b/res/drawable-hdpi/ic_menu_compose.png differ diff --git a/res/drawable-hdpi/ic_menu_delete.png b/res/drawable-hdpi/ic_menu_delete.png new file mode 100644 index 0000000..ce5ecc4 Binary files /dev/null and b/res/drawable-hdpi/ic_menu_delete.png differ diff --git a/res/drawable-hdpi/ic_menu_discard.png b/res/drawable-hdpi/ic_menu_discard.png new file mode 100644 index 0000000..4683f61 Binary files /dev/null and b/res/drawable-hdpi/ic_menu_discard.png differ diff --git a/res/drawable-hdpi/ic_menu_edit.png b/res/drawable-hdpi/ic_menu_edit.png new file mode 100644 index 0000000..4748cda Binary files /dev/null and b/res/drawable-hdpi/ic_menu_edit.png differ diff --git a/res/drawable-hdpi/ic_menu_revert.png b/res/drawable-hdpi/ic_menu_revert.png new file mode 100644 index 0000000..ffc67d9 Binary files /dev/null and b/res/drawable-hdpi/ic_menu_revert.png differ diff --git a/res/drawable-hdpi/ic_menu_save.png b/res/drawable-hdpi/ic_menu_save.png new file mode 100644 index 0000000..62d0b9a Binary files /dev/null and b/res/drawable-hdpi/ic_menu_save.png differ diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..8074c4c Binary files /dev/null and b/res/drawable-hdpi/icon.png differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..1095584 Binary files /dev/null and b/res/drawable-ldpi/icon.png differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..a07c69f Binary files /dev/null and b/res/drawable-mdpi/icon.png differ diff --git a/res/menu/options_menu.xml b/res/menu/options_menu.xml new file mode 100644 index 0000000..b2d14ac --- /dev/null +++ b/res/menu/options_menu.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ 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); + } +}