Commits

Anonymous committed 2a835e1

ver 0.1

Comments (0)

Files changed (16)

 jni/libocamltop.c
 assets/ocaml-stdlib
 
+syntax: regexp
+/\.DS_Store$
+

AndroidManifest.xml

         </activity>
 
     </application>
-    <uses-permission android:name="android.permission.INTERNET"/>
     <uses-sdk android:minSdkVersion="3" />
 
-
 </manifest> 
     - stdlib/*.cm[ai]  
 
 * Generating libcamlrun.a:
-  Follow the instruction at http://sites.google.com/site/keigoattic/ocaml-on-android
+  Follow the instructions at http://sites.google.com/site/keigoattic/ocaml-on-android
 
 * Generating libocamltop.c :
   1. Add the following lines to Makefile in the top level directory of OCaml distribution:
+Thanks for the icon to http://animal.myds.jp/
+
 LOCAL_MODULE    := ocamltop
 LOCAL_SRC_FILES := libocamltop.c start.c
 LOCAL_CFLAGS    := -std=c99 -I$(OCAMLANDROID_PREFIX)/lib/ocaml 
+LOCAL_LDLIBS := -llog
 LOCAL_STATIC_LIBRARIES    := libcamlrun
 
 $(NDK_APP_PROJECT_PATH)/obj/local/armeabi/libcamlrun.a:

jni/jp_co_itpl_ocamlandroid_Native.h

 /*
  * Class:     jp_co_itpl_ocamlandroid_Native
  * Method:    start
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_jp_co_itpl_ocamlandroid_Native_start
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     jp_co_itpl_ocamlandroid_Native
+ * Method:    chdir
  * Signature: (Ljava/lang/String;)V
  */
-JNIEXPORT void JNICALL Java_jp_co_itpl_ocamlandroid_Native_start
+JNIEXPORT void JNICALL Java_jp_co_itpl_ocamlandroid_Native_chdir
   (JNIEnv *, jclass, jstring);
 
 #ifdef __cplusplus
 #include <sys/socket.h>
 #include <sys/un.h>
 
-#define SERVER     "jp.co.itpl.ocamltop.stdinout"
-#define SERVER_ERR "jp.co.itpl.ocamltop.stderr"
+#include <android/log.h>
+
+#define LOG_TAG "ocaml-android"
+#define LOGD(...) do { __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__); } while(0)
+#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
+#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
+#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
+
 
 /*
  * Create a UNIX-domain socket address in the Linux "abstract namespace".
     return 0;
 }
 
-static int waitfor(const char* name) {
+static void start_caml(const jbyte *name) {
+  LOGD("entry: start_caml");
+  struct sockaddr_un sockAddr;
+  socklen_t sockLen;
 
-  struct sockaddr_un server_addr;
-  socklen_t server_addr_len;
-
-  if (makeAddr(name, &server_addr, &server_addr_len) < 0)
-      return 0;
-
-  int listenfd = socket(AF_LOCAL, SOCK_STREAM, PF_UNIX);
-  if (listenfd < 0) {
-      return 0;
+  if (makeAddr(name, &sockAddr, &sockLen) < 0) {
+      LOGE("native: name creation failed");
+      return;
   }
 
-  int serverfd = 0;
-  if (bind(listenfd, (const struct sockaddr*) &server_addr, server_addr_len) < 0) {
-      perror("server bind()");
-      goto bail;
+  int fd = socket(AF_LOCAL, SOCK_STREAM, PF_UNIX);
+  if (fd < 0) {
+      LOGE("native: socket() failed");
+      return;
   }
-  if (listen(listenfd, 5) < 0) {
-      perror("server listen()");
-      goto bail;
-  }
-  serverfd = accept(listenfd, NULL, NULL);
-  if (serverfd < 0) {
-      perror("server accept");
-      serverfd = 0;
-      goto bail;
+  if (connect(fd, (const struct sockaddr*) &sockAddr, sockLen) < 0) {
+      LOGE("native: connect() failed");
+	  close (fd);
+	  return;
   }
 
-bail:
-  if(listenfd>0) close(listenfd);
+  dup2(fd, 0); // stdin  
+  dup2(fd, 1); // stdout
+  dup2(fd, 2); // stderr
 
-  return serverfd;
+  char* p = 0;
+  caml_startup(&p);
+
+  LOGD("exit successful: start_caml");
+  return;
 }
 
-static int server_inout = 0;
-static int server_err = 0;
-
-static int redirect_start() {
-
-  server_err = waitfor(SERVER_ERR);
-  if(server_err==0) return 0;
-  server_inout = waitfor(SERVER);
-  if(server_inout==0) return 0;
-
-  dup2(server_inout, 0); // stdin  
-  dup2(server_inout, 1); // stdout
-  dup2(server_err, 2); // stderr
-
-  return 1;
+JNIEXPORT jboolean JNICALL Java_jp_co_itpl_ocamlandroid_Native_start (JNIEnv *env, jclass clazz, jstring name_) {
+  LOGD("entry: prepare_stream");
+  const jbyte *name = (*env)->GetStringUTFChars(env, name_, NULL);
+  if (name == NULL) {
+    return JNI_FALSE;
+  }
+  
+  pid_t parent_pgid = getpgid(0);
+  if(parent_pgid==-1) { LOGE("getpgid failed."); }
+  
+  if(fork()==0) {
+    LOGI("child:forked caml process");
+    int res = setpgid(0, parent_pgid);
+    if(res==-1) { LOGE("setpgid failed."); }
+    start_caml(name);
+    exit(0);
+  } else {
+    LOGI("parent:forked caml process");
+  }
+  (*env)->ReleaseStringUTFChars(env, name_, name);
+  LOGD("exit successful: prepare_stream");
+  return JNI_TRUE;
 }
 
-static void redirect_end() {
-  // TODO dup2で変えたfdを元に戻す
-  if(server_err!=0) close(server_inout);
-  if(server_inout!=0) close(server_inout);
-}
-
-static void chdir_jstring(JNIEnv * env, jstring path_) {
+JNIEXPORT void JNICALL Java_jp_co_itpl_ocamlandroid_Native_chdir (JNIEnv * env, jclass clazz, jstring path_) {
+  LOGD("entry: chdir");
   const jbyte *path = (*env)->GetStringUTFChars(env, path_, NULL);
   if (path == NULL) {
       return;
   }
   chdir(path);
   (*env)->ReleaseStringUTFChars(env, path_, path);
+  LOGD("exit successful: chdir");
 }
 
-JNIEXPORT void JNICALL Java_jp_co_itpl_ocamlandroid_Native_start (JNIEnv * env, jclass clazz, jstring path) {
 
-  chdir_jstring(env, path);
-  
-  if(!redirect_start()) {
-    redirect_end(); 
-    return;
-  }
-
-  char* p = 0;
-  caml_startup(&p);
-
-  redirect_end();
-  
-}
Add a comment to this file

res/drawable/ic_menu_info_details.png

Added
New image
Add a comment to this file

res/drawable/icon.png

Old
Old image
New
New image

res/layout/about.xml

+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+	android:orientation="vertical" android:layout_width="fill_parent"
+	android:layout_height="fill_parent">
+
+	<TextView android:id="@+id/main" android:layout_width="fill_parent"
+		android:layout_height="wrap_content" android:text="@string/app_name"
+		android:textStyle="bold" android:textSize="20sp" android:gravity="center" />
+
+	<ImageView android:src="@drawable/icon" android:layout_width="fill_parent"
+		android:layout_height="wrap_content" 
+		android:gravity="center"
+		/>
+
+	<TextView android:layout_width="fill_parent"
+		android:layout_height="wrap_content" android:text="@string/copyright"
+		android:gravity="center" />
+
+</LinearLayout>

res/layout/main.xml

     android:orientation="vertical"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
+    android:gravity="fill_vertical"
     >
-<TextView  
+<EditText
+    android:id="@+id/input"
     android:layout_width="fill_parent" 
-    android:layout_height="wrap_content" 
-    android:text="@string/hello"
+    android:layout_height="wrap_content"
+    android:singleLine="true" 
+    />
+<WebView  
+    android:id="@+id/output"
+    android:layout_width="fill_parent" 
+    android:layout_height="fill_parent" 
     />
 </LinearLayout>

res/values/strings.xml

 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="hello">Hello World, MainActivity!</string>
-    <string name="app_name">ocaml-android</string>
+    <string name="app_name">OCaml toplevel</string>
+    <string name='copy'>Copy Text</string>
+    <string name="about">About</string>
+    <string name='copyright'>Copyright &#169; 2010 by Keigo IMAI\n
+Copyright &#169; 1995-2010 by INRIA\n
+The source code of this program is fully available at:\n
+http://bitbucket.org/keigoi/ocaml-toplevel-android\n
+This program links/includes unmodified parts of Objective Caml Compiler and Library.\n
+For each license, see:\n
+http://caml.inria.fr/ocaml/license.en.html</string>
 </resources>

src/jp/co/itpl/ocamlandroid/IS01FullScreen.java

+package jp.co.itpl.ocamlandroid;
+
+import java.lang.reflect.Method;
+
+import android.util.Log;
+
+// make full-screen for IS01 phone
+// from http://blog.kcrt.net/2010/08/17/014820
+public class IS01FullScreen {
+	private static final Method setFullScreenMode;
+
+	static {
+		Method meth = null;
+		try {
+			Class<?> sgManager = Class
+					.forName("jp.co.sharp.android.softguide.SoftGuideManager");
+			Class<?> paramstype[] = { boolean.class };
+			meth = sgManager.getMethod("setFullScreenMode", paramstype);
+		} catch (Exception o) {
+			Log.d("is01fullscreen", "failed" + o.getMessage() + ":"
+					+ o.getClass().toString());
+		}
+		setFullScreenMode = meth;
+	}
+
+	public static void invoke() {
+		try {
+			if (setFullScreenMode != null) {
+				setFullScreenMode.invoke(null, true);
+			}
+		} catch (Exception o) {
+			Log.d("is01fullscreen", "failed");
+		}
+	}
+
+}

src/jp/co/itpl/ocamlandroid/MainActivity.java

 package jp.co.itpl.ocamlandroid;
 
-import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 
 import android.app.Activity;
+import android.app.Dialog;
+import android.graphics.Color;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.widget.EditText;
+import android.widget.TextView;
 
 public class MainActivity extends Activity {
+	public static final int MENU_COPY = 1;
+	public static final int MENU_ABOUT = 2;
 	public static final String TAG = "ocaml-android";
+	private WebView outView; // we use WebView for output so that we easily get copying-capability
+	volatile PrintWriter out = null;
 
-	private static void outForever(final InputStream raw) {
+	private static String jsescape(String line) {
+		return line.replace("'", "\\'").replace("\n", "\\n"); // FIXME
+	}
+	
+	private void println(String line) {
+		Log.d(TAG, "console output:"+line);
+		outView.loadUrl("javascript:document.getElementById('output').innerHTML+='"+ jsescape(line) + "'");
+	}
+
+	private void console_output_forever(final WebView outView, final InputStream in) {
 		new Thread() {
 			public void run() {
 				try {
-			    	String line = null;
-			    	BufferedReader in = new BufferedReader(new InputStreamReader(raw, "utf-8"));
-			    	while((line=in.readLine())!=null) {
-			    		Log.i(TAG, line);
-			    	}
-		        } catch(IOException e) {
-		        	Log.e(TAG, "reading stdout/stderr error", e);
-		        }
+					while (true) {
+						int i = in.read();
+						if(i==-1) {
+							break;
+						}
+						int len = in.available();
+						byte[] b = new byte[len+1];
+						b[0] = (byte)(i <= 127 ? i : i - 256);
+						in.read(b, 1, len);
+						println(new String(b,"utf-8"));
+					}
+				} catch (IOException e) {
+					Log.e(TAG, "reading stdout/stderr error", e);
+				}
 			}
 		}.start();
 	}
+
+	
+	private void beginToplevel() {
+		try {
+			OcamlTop ocaml = new OcamlTop(this);
+			console_output_forever(outView, ocaml.stream.getInputStream());
+
+			out = new PrintWriter(new OutputStreamWriter(
+					ocaml.stream.getOutputStream(), "utf-8"));
+		} catch (IOException e) {
+			Log.e(TAG, "error in main", e);
+		}
+	}
+	
 	
 	@Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.main);        
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.main);
+		outView = (WebView) findViewById(R.id.output);
+		WebSettings webSettings = outView.getSettings();
+		webSettings.setJavaScriptEnabled(true);
+		webSettings.setCacheMode(WebSettings.LOAD_NORMAL);
+		webSettings.setDefaultFontSize(10);
+		outView.setBackgroundColor(Color.BLACK);
+		outView.loadData("<pre style='color:#C0C0C0' id='output'></pre>",
+				"text/html", "utf-8");
+		
+		EditText edit = (EditText)findViewById(R.id.input);
+		edit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+			public boolean onEditorAction(TextView view, int arg1, KeyEvent arg2) {
+				if(out!=null) {
+					String line = view.getText().toString()+"\n";
+					println(line);
+					out.println(line);
+					out.flush();
+					view.setText("");
+				}
+				return true;
+			}
+		});
+		edit.requestFocus();
+		
+		new Thread() {public void run() {beginToplevel();}}.start();
 
-        try {
-            OcamlTop ocaml = new OcamlTop(this);
-	    	outForever(ocaml.stdinout.getInputStream());
-	    	outForever(ocaml.stderr.getInputStream());
-	    	
-	    	PrintWriter out = new PrintWriter(new OutputStreamWriter(ocaml.stdinout.getOutputStream(), "utf-8"));
-	    	out.println("1;;\n");
-	    	out.flush();
-        } catch(IOException e) {
-        	Log.e(TAG, "error in main", e);
-        }
+	}
 
-   }
+	@Override
+	public void onResume() {
+		super.onResume();
+		IS01FullScreen.invoke();
+	}
+
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		super.onCreateOptionsMenu(menu);
+
+		menu.add(Menu.NONE, MENU_COPY, Menu.NONE, R.string.copy);
+		menu.add(Menu.NONE, MENU_ABOUT, Menu.NONE, R.string.about).setIcon(
+				R.drawable.ic_menu_info_details);
+		return true;
+	}
+
+	private void showAboutDialog() {
+		final Dialog dialog = new Dialog(MainActivity.this) {
+			@Override
+			public boolean onTouchEvent(MotionEvent event) {
+				this.dismiss();
+				return true;
+			}
+
+		};
+		dialog.setContentView(R.layout.about);
+		dialog.show();
+	}
+
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		switch (item.getItemId()) {
+		case MENU_COPY:
+			try {
+				// simulate shift key down so that we can use webview's
+				// select-copy functionality
+				KeyEvent shiftPressEvent = new KeyEvent(0, 0,
+						KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0);
+				shiftPressEvent.dispatch(outView);
+			} catch (Exception e) {
+				throw new AssertionError(e);
+			}
+			return true;
+		case MENU_ABOUT:
+			showAboutDialog();
+			return true;
+		default:
+			return super.onOptionsItemSelected(item);
+		}
+	}
 }

src/jp/co/itpl/ocamlandroid/Native.java

 		System.loadLibrary("ocamltop");
 	}
 
-	public static native void start(String name);
+	public static native boolean start(String connectname);
+	public static native void chdir(String path);
 	
 }

src/jp/co/itpl/ocamlandroid/OcamlTop.java

 
 import android.content.Context;
 import android.content.res.AssetManager;
+import android.net.LocalServerSocket;
 import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
 import android.util.Log;
 
 public class OcamlTop {
 	public static final String TAG = "ocaml-android";
 	public static final String ASSETS_STDLIBPATH = "ocaml-stdlib";
-	public static final String STDINOUT = "jp.co.itpl.ocamltop.stdinout";
-	public static final String STDERR = "jp.co.itpl.ocamltop.stderr";
+	public static final String STDSTREAM_NAME = "jp.co.itpl.ocamltop";
 
 	private void prepareStdlib(Context context) {
 		File cache = context.getCacheDir();
 		AssetManager assets = context.getAssets();
 		try {
 			String[] files = assets.list(ASSETS_STDLIBPATH);
-			Log.d(TAG,"copying "+files.length+" files of stdlib..");
-			for(String file : files) {
+			Log.d(TAG, "copying " + files.length + " files of stdlib..");
+			for (String file : files) {
 				InputStream in = null;
 				OutputStream out = null;
 				try {
-					in = assets.open(ASSETS_STDLIBPATH+File.separator+file);
-					String outfile = cache.getAbsolutePath()+File.separator+file;
+					in = assets.open(ASSETS_STDLIBPATH + File.separator + file);
+					String outfile = cache.getAbsolutePath() + File.separator
+							+ file;
 					out = new FileOutputStream(outfile);
 					IOUtils.copy(in, out);
 				} finally {
 					IOUtils.closeQuietly(in);
-					IOUtils.closeQuietly(out);					
+					IOUtils.closeQuietly(out);
 				}
 			}
-			Log.d(TAG,"copy complete.");
-		} catch(IOException e) {
-			Log.e(TAG, "stdlib preparation error", e);
+			Log.d(TAG, "copy complete.");
+		} catch (IOException e) {
+			Log.e(TAG, "stdlib copy error", e);
 			// FIXME: exit
 		}
 	}
-	
-	private static void sleep(long millis) {
-		try{ Thread.sleep(millis); } catch(InterruptedException e) { Thread.currentThread().interrupt(); }
+
+	public final LocalSocket stream;
+
+	public OcamlTop(final Context context) throws IOException {
+		prepareStdlib(context);
+		Native.chdir(context.getCacheDir().getAbsolutePath());
+
+		Log.i(TAG, "connecting std stream");
+		LocalServerSocket listenSock = new LocalServerSocket(STDSTREAM_NAME);
+		new Thread() {
+			public void run() {
+				Native.start(STDSTREAM_NAME);
+				Log.i(TAG, "ocaml thread started");
+			}
+		}.start();
+		stream = listenSock.accept();
+		Log.i(TAG, "connected std stream");
 	}
-	
-	
-    public final LocalSocket stdinout = new LocalSocket();
-    public final LocalSocket stderr = new LocalSocket();
-
-    public OcamlTop(final Context context) throws IOException {
-    	// FIXME: stdin/out, stderr の接続を個別の関数に分ければsleepは不要になる
-        new Thread(){ public void run() { Native.start(context.getCacheDir().getAbsolutePath()); } }.start();
-    	Log.i(TAG, "thread started");
-        
-        prepareStdlib(context);
-
-    	Log.i(TAG, "connecting");
-    	sleep(500);
-    	stderr.connect(new LocalSocketAddress(STDERR, LocalSocketAddress.Namespace.ABSTRACT));
-    	Log.i(TAG, "stderr connected");
-    	sleep(500);
-    	stdinout.connect(new LocalSocketAddress(STDINOUT, LocalSocketAddress.Namespace.ABSTRACT));
-    	Log.i(TAG, "stdin/out connected");
-
-    }
 }
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.