/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.momodalo.app.vimtouch;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kvj.app.vimtouch.ext.manager.IntegrationManager;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
/**
* Utility methods for creating and managing a subprocess.
* <p>
* Note: The native methods access a package-private
* java.io.FileDescriptor field to get and set the raw Linux
* file descriptor. This might break if the implementation of
* java.io.FileDescriptor is changed.
*/
public class Exec
{
static {
System.loadLibrary("vimtouch");
}
static final String TAG = "Exec";
static public VimTouch vimtouch;
static private int dialogState = 0;
static private int dialogDefaultState = 0;
static private int State;
static private int Cursor_col;
static private int Cursor_lnum;
static public final int DIALOG_INPROGRESS = -1;
static public void resultDialogState(int state){
dialogState = state;
returnDialog(dialogState);
}
static public void resultDialogDefaultState(){
dialogState = dialogDefaultState;
returnDialog(dialogState);
}
public static void showDialog(
int type, String title, String message,
String buttons, int default_button, String textfield
) {
dialogState = DIALOG_INPROGRESS;
dialogDefaultState = default_button;
vimtouch.nativeShowDialog(type,title, message, buttons, default_button, textfield);
}
public static void quit() {
vimtouch.hideIme();
vimtouch.finish();
}
public static void setCurTab(int n){
vimtouch.nativeSetCurTab(n);
}
public static void showTab(int n){
vimtouch.nativeShowTab(n);
}
public static void setTabLabels(String[] labels){
vimtouch.nativeSetTabs(labels);
}
public static void setClipText(String text){
vimtouch.nativeSetClipText(text);
}
public static String getClipText() throws InterruptedException {
vimtouch.nativeSyncClipText();
String clipText = vimtouch.getClipText();
while (clipText == null) {
Thread.sleep(100);
clipText = vimtouch.getClipText();
}
return clipText;
}
/**
* Create a subprocess. Differs from java.lang.ProcessBuilder in
* that a pty is used to communicate with the subprocess.
* <p>
* Callers are responsible for calling Exec.close() on the returned
* file descriptor.
*
* @param cmd The command to execute
* @param arg0 The first argument to the command, may be null
* @param arg1 the second argument to the command, may be null
* started process will be written.
* @return the file descriptor of the started process.
*
*/
public static native FileDescriptor nativeCreateSubprocess(
String cmd, String filepath, String sock, String arg0, String arg1, String[] envp);
public static native void startVim();
/**
* Set the widow size for a given pty. Allows programs
* connected to the pty learn how large their screen is.
*/
public static native void setPtyWindowSize(FileDescriptor fd,
int row, int col, int xpixel, int ypixel);
public static native void setPtyUTF8Mode(FileDescriptor fd,
boolean utf8Mode);
public static native int scrollBy(int processId);
/**
* Causes the calling thread to wait for the process associated with the
* receiver to finish executing.
*
* @return The exit value of the Process being waited on
*
*/
public static native int nativeWait();
/**
* Close a given file descriptor.
*/
public static native void close(FileDescriptor fd);
public static native void updateScreen();
public static native void doCommand(String cmd);
public static native void mouseDown(int row, int col);
public static native void mouseDrag(int row, int col);
public static native void mouseUp(int row, int col);
public static native void lineReplace(String line);
public static native String getCurrentLine(int size);
public static native void setCursorCol(int col);
public static native void setCursorPos(int row, int col);
public static native String getCurrBuffer();
public static native void setTab(int nr);
public static native String getcwd();
public static native void setSocket(int fd);
public static native void returnClipText(String text);
public static native void returnDialog(int s);
public static native void returnExtensionResult(String text);
public static native void sendAndroidEvent(int type, String object);
public static native void getHistory();
public static int getCursorLine() {
return Cursor_lnum;
}
public static int getCursorCol() {
return Cursor_col;
}
public static int getState(){
return State;
}
public static void syncVim(int s, int c, int l){
State = s;
Cursor_col = c;
Cursor_lnum = l;
}
public final static int CMDLINE = 0x08;
public final static int INSERT = 0x10;
public static boolean isCmdLine(){
return (Exec.getState() & CMDLINE) != 0;
}
public static boolean isInsertMode(){
return (Exec.getState() & INSERT) != 0;
}
public static void launchCommandService() {
}
static VimTouchSocketServer mSocketServer;
public static FileDescriptor createSubprocess(
String cmd, String filepath, String arg0, String arg1, String[] envp){
mSocketServer = new VimTouchSocketServer();
mSocketServer.start();
return nativeCreateSubprocess( cmd, filepath, mSocketServer.getSocketName(), arg0, arg1, envp);
}
public static class VimTouchSocketServer extends Thread {
int mBufferSize = 8192;
byte[] mBuffer;
LocalServerSocket mServerSocket;
LocalSocket mReceiver;
static final String VIMTOUCH_SOCKET = "/tmp/vimtouch/";
String mSocketName;
public VimTouchSocketServer() {
mBuffer = new byte[mBufferSize];
try {
mSocketName = UUID.randomUUID().toString();
mServerSocket = new LocalServerSocket(VIMTOUCH_SOCKET + mSocketName);
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "The localSocketServer created failed !!!");
e.printStackTrace();
}
LocalSocketAddress localSocketAddress;
localSocketAddress = mServerSocket.getLocalSocketAddress();
String str = localSocketAddress.getName();
Log.d(TAG, "The LocalSocketAddress = " + str);
}
public String getSocketName() {
return mSocketName;
}
private static final Pattern COMMAND_REXP = Pattern
.compile("([A-Z ]{7}):");
/**
* Splits str with commands by regexp. Puts commands and values into
* List
*
* @param str
* @return
*/
private List<String> splitCommands(String str) {
Matcher m = COMMAND_REXP.matcher(str);
String name = null;
List<String> result = new ArrayList<String>();
while (m.find()) {
StringBuffer sb = new StringBuffer();
m.appendReplacement(sb, "");
if (null != name) {
// Not a first time, name is set
result.add(name);
result.add(sb.toString());
}
name = m.group(1).trim();
}
StringBuffer sb = new StringBuffer();
m.appendTail(sb);
if (null != name) {
// Last time, name (if set) and tail as a value
result.add(name);
result.add(sb.toString());
}
return result;
}
public void run() {
try {
while (!isInterrupted()) {
if (null == mServerSocket){
break;
}
try {
mReceiver = mServerSocket.accept();
} catch (IOException e) {
Log.d(TAG, "localSocketServer accept() failed !!!");
e.printStackTrace();
continue;
}
Log.e(TAG, "localSocketServer accepted");
InputStream input;
try {
input = mReceiver.getInputStream();
} catch (IOException e) {
Log.d(TAG, "getInputStream() failed !!!");
e.printStackTrace();
continue;
}
Field __fd;
try {
__fd = FileDescriptor.class.getDeclaredField("descriptor");
__fd.setAccessible(true);
setSocket(__fd.getInt(mReceiver.getFileDescriptor()));
} catch (Exception ex) {
__fd = null;
Log.e(TAG,"set socket error " + ex);
}
int bytesRead;
while (mReceiver != null) {
try {
bytesRead = input.read(mBuffer, 0, mBufferSize );
} catch (Exception e) {
Log.d(TAG, "There is an exception when reading socket");
e.printStackTrace();
break;
}
if(bytesRead <= 0)continue;
String str = new String(mBuffer, 0, bytesRead);
List<String> parts = splitCommands(str);
Log.d(TAG, "get "+str);
for (int i = 0; i < parts.size() - 1; i += 2) {
// Process commands (name, value, name, value ...)
String name = parts.get(i);
String value = parts.get(i + 1).trim();
if (name.equals("SETCTAB")) {
setCurTab(Integer.parseInt(value));
} else if (name.equals("SYNC")) {
String[] array = value.split(",");
syncVim(Integer.parseInt(array[0]),
Integer.parseInt(array[1]),
Integer.parseInt(array[2]));
} else if (name.equals("SHOWTAB")) {
showTab(Integer.parseInt(value));
} else if (name.equals("ANDROID")) {
processExtensionCommand(value);
} else if (name.equals("SETLBLS")) {
setTabLabels(value.split(","));
} else if (name.equals("SETCLIP")) {
setClipText(value);
} else if (name.equals("GETCLIP")) {
try {
returnClipText(getClipText());
} catch (Exception e) {
returnClipText("");
}
} else if (name.equals("SDIALOG")) {
String[] array = value.split(",");
showDialog(Integer.parseInt(array[0]),
array[1], array[2], array[3],
Integer.parseInt(array[4]), array[5]);
} else if (name.equals("HISTORY")) {
vimtouch.setHistoryItem(
Integer.parseInt(value.substring(0, 1)),
value.substring(2));
}
}
}
}
Log.d(TAG, "The LocalSocketServer thread is going to stop !!!");
if (mReceiver != null){
try {
mReceiver.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (mServerSocket != null){
try {
mServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} finally {
try {
if (mServerSocket!= null) {
mServerSocket.close();
}
Log.i(TAG, "socket listener stopped");
} catch (IOException e) {
}
}
}
public void stopServer() {
if (mServerSocket != null) {
try {
// mark thread as interrupted
interrupt();
// now send connect request to myself to trigger leaving accept()
LocalSocket ls = new LocalSocket();
ls.connect(mServerSocket.getLocalSocketAddress());
ls.close();
} catch (IOException e) {
Log.e(TAG, "stopSocketServer failed", e);
}
}
}
}
private static void processExtensionCommand(String value) {
int space = value.indexOf(' ');
if (-1 == space) {
returnExtensionResult("");
return;
}
String type = value.substring(0, space);
String params = value.substring(space+1);
// Log.i(TAG, "Extension command: "+type+" - "+params);
returnExtensionResult(IntegrationManager.getInstance(vimtouch).process(
type, params));
}
}