~moody/drawterm

9620904ebb8cf2eea22a1a81cb9bb5274dda710d — Lorenzo Bivens 5 months ago a130d44
Merging echoline's android and fbdev forks
A Make.android => Make.android +38 -0
@@ 0,0 1,38 @@
# Android
SDKPREFIX=$(HOME)/Android/Sdk
JAVA_HOME=/usr
OBJS=lib/arm64-v8a/libdrawterm.so lib/armeabi-v7a/libdrawterm.so lib/x86/libdrawterm.so lib/x86_64/libdrawterm.so

all: drawterm.apk

clean:
	rm -f *.apk lib/*/*.so

lib/arm64-v8a/libdrawterm.so:
	CONF=android-arm64 make -j5;
	CONF=android-arm64 make clean;

lib/armeabi-v7a/libdrawterm.so:
	CONF=android-arm make -j5;
	CONF=android-arm make clean;

lib/x86/libdrawterm.so:
	CONF=android-386 make -j5;
	CONF=android-386 make clean;

lib/x86_64/libdrawterm.so:
	CONF=android-amd64 make -j5;
	CONF=android-amd64 make clean;

drawterm.apk: drawterm-signed.apk
	$(SDKPREFIX)/build-tools/30.0.3/zipalign -v -f 4 $< $@

drawterm-signed.apk: drawterm-unsigned.apk drawterm.keystore
	$(JAVA_HOME)/bin/jarsigner -verbose -keystore ./drawterm.keystore -storepass glendarocks -keypass glendarocks -signedjar $@ $< drawtermKey

drawterm-unsigned.apk: $(OBJS)
	$(SDKPREFIX)/build-tools/30.0.3/aapt package -v -f -M gui-android/AndroidManifest.xml -S gui-android/res -I $(SDKPREFIX)/platforms/android-21/android.jar -F $@ gui-android/bin
	$(SDKPREFIX)/build-tools/30.0.3/aapt add $@ $(OBJS)

drawterm.keystore:
	$(JAVA_HOME)/bin/keytool -genkeypair -validity 1000 -dname "CN=9front,O=Android,C=US" -keystore $@ -storepass glendarocks -keypass glendarocks -alias drawtermKey -keyalg RSA -v

A Make.android-386 => Make.android-386 +26 -0
@@ 0,0 1,26 @@
# Android
SDKPREFIX=$(HOME)/Android/Sdk
NDKPREFIX=$(SDKPREFIX)/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/bin
JAVA_HOME=/usr

PTHREAD=-pthread
AR=$(NDKPREFIX)/i686-linux-android-ar
AS=$(NDKPREFIX)/i686-linux-android-as
RANLIB=$(NDKPREFIX)/i686-linux-android-ranlib
STRIP=$(NDKPREFIX)/i686-linux-android-strip
CC=$(NDKPREFIX)/i686-linux-android21-clang
CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -Dmain=dt_main -fPIC
O=o
OS=posix
GUI=android
LDADD=-ggdb -lm -shared -llog -landroid
LDFLAGS=$(PTHREAD)
TARG=lib/x86/libdrawterm.so
AUDIO=none

all: default

libmachdep.a:
	arch=386; \
	(cd posix-$$arch &&  make)


A Make.android-amd64 => Make.android-amd64 +26 -0
@@ 0,0 1,26 @@
# Android
SDKPREFIX=$(HOME)/Android/Sdk
NDKPREFIX=$(SDKPREFIX)/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/bin
JAVA_HOME=/usr

PTHREAD=-pthread
AR=$(NDKPREFIX)/x86_64-linux-android-ar
AS=$(NDKPREFIX)/x86_64-linux-android-as
RANLIB=$(NDKPREFIX)/x86_64-linux-android-ranlib
STRIP=$(NDKPREFIX)/x86_64-linux-android-strip
CC=$(NDKPREFIX)/x86_64-linux-android21-clang
CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -Dmain=dt_main -fPIC
O=o
OS=posix
GUI=android
LDADD=-ggdb -lm -shared -llog -landroid
LDFLAGS=$(PTHREAD)
TARG=lib/x86_64/libdrawterm.so
AUDIO=none

all: default

libmachdep.a:
	arch=amd64; \
	(cd posix-$$arch &&  make)


A Make.android-arm => Make.android-arm +26 -0
@@ 0,0 1,26 @@
# Android
SDKPREFIX=$(HOME)/Android/Sdk
NDKPREFIX=$(SDKPREFIX)/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/bin
JAVA_HOME=/usr

PTHREAD=-pthread
AR=$(NDKPREFIX)/arm-linux-androideabi-ar
AS=$(NDKPREFIX)/arm-linux-androideabi-as
RANLIB=$(NDKPREFIX)/arm-linux-androideabi-ranlib
STRIP=$(NDKPREFIX)/arm-linux-androideabi-strip
CC=$(NDKPREFIX)/armv7a-linux-androideabi21-clang
CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -Dmain=dt_main -fPIC
O=o
OS=posix
GUI=android
LDADD=-ggdb -lm -shared -llog -landroid
LDFLAGS=$(PTHREAD)
TARG=lib/armeabi-v7a/libdrawterm.so
AUDIO=none

all: default

libmachdep.a:
	arch=arm; \
	(cd posix-$$arch &&  make)


A Make.android-arm64 => Make.android-arm64 +26 -0
@@ 0,0 1,26 @@
# Android
SDKPREFIX=$(HOME)/Android/Sdk
NDKPREFIX=$(SDKPREFIX)/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/bin
JAVA_HOME=/usr

PTHREAD=-pthread
AR=$(NDKPREFIX)/aarch64-linux-android-ar
AS=$(NDKPREFIX)/aarch64-linux-android-as
RANLIB=$(NDKPREFIX)/aarch64-linux-android-ranlib
STRIP=$(NDKPREFIX)/aarch64-linux-android-strip
CC=$(NDKPREFIX)/aarch64-linux-android21-clang
CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -Dmain=dt_main -fPIC
O=o
OS=posix
GUI=android
LDADD=-ggdb -lm -shared -llog -landroid
LDFLAGS=$(PTHREAD)
TARG=lib/arm64-v8a/libdrawterm.so
AUDIO=none

all: default

libmachdep.a:
	arch=arm64; \
	(cd posix-$$arch &&  make)


A Make.fbdev => Make.fbdev +22 -0
@@ 0,0 1,22 @@
# Unix
#PTHREAD=	# for Mac
PTHREAD=-pthread
AR=ar
AS=as
RANLIB=ranlib
CC=gcc
CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -D_THREAD_SAFE $(PTHREAD) -O2
O=o
OS=posix
GUI=fbdev
LDADD=-ggdb -lm -lasound
LDFLAGS=$(PTHREAD)
TARG=drawterm
# AUDIO=none
AUDIO=alsa

all: default

libmachdep.a:
	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/; s/armv[567].*/arm/; s/aarch64/arm64/'`; \
	(cd posix-$$arch &&  make)

M README => README +11 -0
@@ 18,6 18,17 @@ To build on Mac OS X with X11 (xquartz), run CONF=osx-x11 make.

To build on Mac OS X with Cocoa, run CONF=osx-cocoa make and "cp drawterm gui-cocoa/drawterm.app/".

To build for Android, make sure Make.android* and gui-android/Makefile are correct for your build and target systems, then run make -f Make.android

USAGE
-------
On Android the five checkboxes at the top represent the three mouse buttons and mousewheel, determining which "buttons" are clicked. The "kb" button toggles the soft keyboard.


CAVEATS
--------
Be aware that right now on Android the login details are saved as a plaintext string if saved, and there is no secstore support.


BINARIES
---------

A gui-android/AndroidManifest.xml => gui-android/AndroidManifest.xml +30 -0
@@ 0,0 1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.echoline.drawterm">

    <supports-screens android:largeScreens="true"
        android:normalScreens="true" android:smallScreens="true"
        android:anyDensity="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:windowSoftInputMode="stateUnchanged|adjustNothing">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
    <!--<uses-permission android:name="android.permission.SET_DEBUG_APP"/>-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>
</manifest>

A gui-android/Makefile => gui-android/Makefile +23 -0
@@ 0,0 1,23 @@
ROOT=..
include ../Make.config
LIB=libgui.a

OFILES=\
	cpp/android.$O\
	cpp/native-lib.$O\
	cpp/devandroid.$O\

default: $(LIB) gen/org/echoline/drawterm/R.java bin/classes.dex
$(LIB): $(OFILES)
	$(AR) r $(LIB) $(OFILES)
	$(RANLIB) $(LIB)

gen/org/echoline/drawterm/R.java: $(shell find res/ -type f)
	$(SDKPREFIX)/build-tools/30.0.3/aapt package -f -m -M AndroidManifest.xml -I $(SDKPREFIX)/platforms/android-21/android.jar -S res/ -J gen

bin/classes.dex: obj/org/echoline/drawterm/MainActivity.class obj/org/echoline/drawterm/DrawTermThread.class obj/org/echoline/drawterm/MySurfaceView.class
	$(SDKPREFIX)/build-tools/30.0.3/dx --dex --verbose --output=$@ obj/

obj/org/echoline/drawterm/%.class: java/org/echoline/drawterm/%.java
	$(JAVA_HOME)/bin/javac -d obj/ -classpath $(SDKPREFIX)/platforms/android-21/android.jar -sourcepath java java/org/echoline/drawterm/$*.java gen/org/echoline/drawterm/R.java


A gui-android/cpp/android.c => gui-android/cpp/android.c +230 -0
@@ 0,0 1,230 @@
#include <jni.h>
#include <android/native_window.h>
#include <android/log.h>

#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"

#include <draw.h>
#include <memdraw.h>
#include <keyboard.h>
#include <cursor.h>
#include "screen.h"

Memimage *gscreen = nil;
extern int screenWidth;
extern int screenHeight;
extern ANativeWindow *window;
extern jobject mainActivityObj;
extern JavaVM *jvm;

char*
clipread(void)
{
	char *ret;
	const char *s;
	JNIEnv *env;
	jint rs = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
	if (rs != JNI_OK) {
		__android_log_print(ANDROID_LOG_WARN, "drawterm", "AttachCurrentThread returned error: %d", rs);
		return strdup("");
	}
	jclass clazz = (*env)->GetObjectClass(env, mainActivityObj);
	jmethodID methodID = (*env)->GetMethodID(env, clazz, "getClipBoard", "()Ljava/lang/String;");
        jstring str = (jstring)(*env)->CallObjectMethod(env, mainActivityObj, methodID);
	s = (*env)->GetStringUTFChars(env, str, NULL);
	ret = strdup(s);
	(*env)->ReleaseStringUTFChars(env, str, s);
	(*jvm)->DetachCurrentThread(jvm);
	return ret;
}

int
clipwrite(char *buf)
{
	JNIEnv *env;
	jint rs = (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6);
	if(rs != JNI_OK) {
		__android_log_print(ANDROID_LOG_WARN, "drawterm", "GetEnv returned error: %d", rs);
		return 0;
	}
	jclass clazz = (*env)->GetObjectClass(env, mainActivityObj);
	jmethodID methodID = (*env)->GetMethodID(env, clazz, "setClipBoard", "(Ljava/lang/String;)V");
        jstring str = (*env)->NewStringUTF(env, buf);
	(*env)->CallVoidMethod(env, mainActivityObj, methodID, str);
	return 0;
}

void
show_notification(char *buf)
{
	JNIEnv *env;
	jint rs = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
	if(rs != JNI_OK) {
		__android_log_print(ANDROID_LOG_WARN, "drawterm", "AttachCurrentThread returned error: %d", rs);
		return;
	}
	jclass clazz = (*env)->GetObjectClass(env, mainActivityObj);
	jmethodID methodID = (*env)->GetMethodID(env, clazz, "showNotification", "(Ljava/lang/String;)V");
        jstring str = (*env)->NewStringUTF(env, buf);
	(*env)->CallVoidMethod(env, mainActivityObj, methodID, str);
	(*jvm)->DetachCurrentThread(jvm);
	return;
}

int
num_cameras()
{
	JNIEnv *env;
	int n;
	jint rs = (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6);
	if(rs != JNI_OK) {
		__android_log_print(ANDROID_LOG_WARN, "drawterm", "GetEnv returned error: %d", rs);
		return 0;
	}
	jclass clazz = (*env)->GetObjectClass(env, mainActivityObj);
	jmethodID methodID = (*env)->GetMethodID(env, clazz, "numCameras", "()I");
	n = (*env)->CallIntMethod(env, mainActivityObj, methodID);
	return n;
}

void
take_picture(int id)
{
	JNIEnv *env;
	jint rs = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
	if(rs != JNI_OK) {
		__android_log_print(ANDROID_LOG_WARN, "drawterm", "AttachCurrentThread returned error: %d", rs);
		return;
	}
	jclass clazz = (*env)->GetObjectClass(env, mainActivityObj);
	jmethodID methodID = (*env)->GetMethodID(env, clazz, "takePicture", "(I)V");
	(*env)->CallVoidMethod(env, mainActivityObj, methodID, id);
	(*jvm)->DetachCurrentThread(jvm);
	return;
}

void
setcolor(ulong i, ulong r, ulong g, ulong b)
{
	return;
}

void
getcolor(ulong v, ulong *r, ulong *g, ulong *b)
{
	*r = (v>>16)&0xFF;
	*g = (v>>8)&0xFF;
	*b = v&0xFF;
}

void
flushmemscreen(Rectangle r)
{
	ANativeWindow_Buffer buffer;
	uint8_t *pixels;
	int x, y, o, b;
	ARect bounds;

	if (window == NULL)
		return;

	memset(&buffer, 0, sizeof(buffer));

	bounds.left = r.min.x;
	bounds.top = r.min.y;
	bounds.right = r.max.x;
	bounds.bottom = r.max.y;

	if (ANativeWindow_lock(window, &buffer, &bounds) != 0) {
		__android_log_print(ANDROID_LOG_WARN, "drawterm", "Unable to lock window buffer");
		return;
	}

	r.min.x = bounds.left;
	r.min.y = bounds.top;
	r.max.x = bounds.right;
	r.max.y = bounds.bottom;

	pixels = (uint8_t*)buffer.bits;
	for (y = r.min.y; y < r.max.y; y++)
		for (x = r.min.x; x < r.max.x; x++) {
			o = (y * screenWidth + x) * 4;
			b = (y * buffer.stride + x) * 4;
			pixels[b+3] = 0xFF;
			pixels[b+2] = gscreen->data->bdata[o+0];
			pixels[b+1] = gscreen->data->bdata[o+1];
			pixels[b+0] = gscreen->data->bdata[o+2];
		}

	if (ANativeWindow_unlockAndPost(window) != 0) {
		__android_log_print(ANDROID_LOG_WARN, "drawterm", "Unable to unlock and post window buffer");
	}
	return;
}

void
screeninit(void)
{
	Rectangle r = Rect(0,0,screenWidth,screenHeight);
	memimageinit();
	screensize(r, XRGB32);
	if (gscreen == nil)
		panic("screensize failed");
	gscreen->clipr = r;
	terminit();
	qlock(&drawlock);
	flushmemscreen(r);
	qunlock(&drawlock);
	return;
}

void
screensize(Rectangle r, ulong chan)
{
	Memimage *mi;

	mi = allocmemimage(r, chan);
	if (mi == nil)
		return;

	if (gscreen != nil)
		freememimage(gscreen);

	gscreen = mi;
	gscreen->clipr = ZR;
}

Memdata*
attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
{
	*r = gscreen->clipr;
	*depth = gscreen->depth;
	*chan = gscreen->chan;
	*width = gscreen->width;
	*softscreen = 1;

	gscreen->data->ref++;
	return gscreen->data;
}

void
setcursor(void)
{
	return;
}

void
mouseset(Point xy)
{
	return;
}

void
guimain(void)
{
	cpubody();
}


A gui-android/cpp/devandroid.c => gui-android/cpp/devandroid.c +248 -0
@@ 0,0 1,248 @@
#include	"u.h"
#include	"lib.h"
#include	"dat.h"
#include	"fns.h"
#include	"error.h"

#include <android/log.h>
#include <android/sensor.h>

void show_notification(char *buf);
void take_picture(int id);
int num_cameras();

int Ncameras = 0;

uchar *cambuf = nil;
int camlen;

ASensorManager *sensorManager = NULL;

enum
{
	Qdir		= 0,
	Qcam		= 1,
	Qaccel		= 2,
	Qcompass	= 4,
	Qnotification	= 6,
};
#define QID(p, c, y) 	(((p)<<16) | ((c)<<4) | (y))

static void androidinit(void);

static void
androidinit(void)
{
	sensorManager = ASensorManager_getInstance();

	Ncameras = num_cameras();
}

static Chan*
androidattach(char *param)
{
	Chan *c;

	c = devattach('N', param);
	c->qid.path = QID(0, 0, Qdir);
	c->qid.type = QTDIR;
	c->qid.vers = 0;

	return c;
}

static int
androidgen(Chan *c, char *n, Dirtab *d, int nd, int s, Dir *dp)
{
	Qid q;

	if (s == DEVDOTDOT) {
		mkqid(&q, Qdir, 0, QTDIR);
		devdir(c, q, "#N", 0, eve, 0555, dp);
		return 1;
	}
	if (s < Ncameras) {
		sprintf(up->genbuf, "cam%d.jpg", s);
		mkqid(&q, (s << 16) | Qcam, 0, QTFILE);
		devdir(c, q, up->genbuf, 0, eve, 0444, dp);
		return 1;
	}
	if (s == Ncameras) {
		sprintf(up->genbuf, "accel");
		mkqid(&q, Qaccel, 0, QTFILE);
		devdir(c, q, up->genbuf, 0, eve, 0444, dp);
		return 1;
	}
	if (s == (Ncameras+1)) {
		sprintf(up->genbuf, "compass");
		mkqid(&q, Qcompass, 0, QTFILE);
		devdir(c, q, up->genbuf, 0, eve, 0444, dp);
		return 1;
	}
	if (s == (Ncameras+2)) {
		sprintf(up->genbuf, "notification");
		mkqid(&q, Qnotification, 0, QTFILE);
		devdir(c, q, up->genbuf, 0, eve, 0222, dp);
		return 1;
	}
	return -1;
}

static Walkqid*
androidwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, 0, 0, androidgen);
}

static int
androidstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, 0, 0, androidgen);
}

static Chan*
androidopen(Chan *c, int omode)
{
	p9_uvlong s;

	c = devopen(c, omode, 0, 0, androidgen);

	if (c->qid.path & Qcam) {
		s = c->qid.path >> 16;
		take_picture(s);
	}
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	c->iounit = 8192;

	return c;
}

static void
androidclose(Chan *c)
{
	if (c->qid.path & Qcam && cambuf != nil) {
		free(cambuf);
		cambuf = nil;
	}
}

static long
androidread(Chan *c, void *v, long n, vlong off)
{
	char *a = v;
	long l;
	const ASensor *sensor;
	ASensorEventQueue *queue = NULL;
	ASensorEvent data;

	switch((ulong)c->qid.path & 0xF) {
		default:
			error(Eperm);
			return -1;

		case Qcam:
			while(cambuf == nil)
				usleep(10 * 1000);

			l = camlen - off;
			if (l > n)
				l = n;

			if (l > 0)
				memcpy(a, cambuf + off, l);

			return l;
		case Qaccel:
			queue = ASensorManager_createEventQueue(sensorManager, ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS), 1, NULL, NULL);
			if (queue == NULL)
				return 0;
			sensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_ACCELEROMETER);
			if (sensor == NULL) {
				ASensorManager_destroyEventQueue(sensorManager, queue);
				return 0;
			}
			if (ASensorEventQueue_enableSensor(queue, sensor)) {
				ASensorEventQueue_disableSensor(queue, sensor);
				ASensorManager_destroyEventQueue(sensorManager, queue);
				return 0;
			}
			l = 0;
			if (ALooper_pollAll(1000, NULL, NULL, NULL) == 1) {
				if (ASensorEventQueue_getEvents(queue, &data, 1)) {
					l = snprint(a, n, "%11f %11f %11f\n", data.vector.x, data.vector.y, data.vector.z);
				}
			}
			ASensorEventQueue_disableSensor(queue, sensor);
			ASensorManager_destroyEventQueue(sensorManager, queue);
			return l;
		case Qcompass:
			queue = ASensorManager_createEventQueue(sensorManager, ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS), 1, NULL, NULL);
			if (queue == NULL)
				return 0;
			sensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_MAGNETIC_FIELD);
			if (sensor == NULL) {
				ASensorManager_destroyEventQueue(sensorManager, queue);
				return 0;
			}
			if (ASensorEventQueue_enableSensor(queue, sensor)) {
				ASensorEventQueue_disableSensor(queue, sensor);
				ASensorManager_destroyEventQueue(sensorManager, queue);
				return 0;
			}
			l = 0;
			if (ALooper_pollAll(1000, NULL, NULL, NULL) == 1) {
				if (ASensorEventQueue_getEvents(queue, &data, 1)) {
					l = snprint(a, n, "%11f %11f %11f\n", data.vector.x, data.vector.y, data.vector.z);
				}
			}
			ASensorEventQueue_disableSensor(queue, sensor);
			ASensorManager_destroyEventQueue(sensorManager, queue);
			return l;
		case Qdir:
			return devdirread(c, a, n, 0, 0, androidgen);
	}
}

static long
androidwrite(Chan *c, void *vp, long n, vlong off)
{
	char *a = vp;
	char *str;

	switch((ulong)c->qid.path) {
		case Qnotification:
			str = malloc(n+1);
			memcpy(str, a, n);
			str[n] = '\0';
			show_notification(str);
			free(str);
			return n;
		default:
			error(Eperm);
			break;
	}
	return -1;
}

Dev androiddevtab = {
	'N',
	"android",

	devreset,
	androidinit,
	devshutdown,
	androidattach,
	androidwalk,
	androidstat,
	androidopen,
	devcreate,
	androidclose,
	androidread,
	devbread,
	androidwrite,
	devbwrite,
	devremove,
	devwstat,
};

A gui-android/cpp/native-lib.c => gui-android/cpp/native-lib.c +173 -0
@@ 0,0 1,173 @@
#include <jni.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <android/log.h>
#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
#include <draw.h>
#include <string.h>
#include <keyboard.h>

void absmousetrack(int, int, int, ulong);
ulong ticks(void);
int dt_main(int, char**);
int screenWidth;
int screenHeight;
Point mousept = {0, 0};
int buttons = 0;
float ws = 1;
float hs = 1;
extern char *snarfbuf;
int mPaused = 0;
ANativeWindow *window = NULL;
jobject mainActivityObj;
JavaVM *jvm;
void flushmemscreen(Rectangle r);
extern uchar *cambuf;
extern int camlen;

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setObject(
        JNIEnv *env,
        jobject obj) {
    mainActivityObj = (*env)->NewGlobalRef(env, obj);
    jint rs = (*env)->GetJavaVM(env, &jvm);
    assert(rs == JNI_OK);
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_keyDown(
        JNIEnv *env,
        jobject obj,
        jint c) {
    kbdkey(c, 1);
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_keyUp(
        JNIEnv *env,
        jobject obj,
        jint c) {
    kbdkey(c, 0);
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setPass(
        JNIEnv *env,
        jobject obj,
        jstring str) {
    setenv("PASS", (char*)(*env)->GetStringUTFChars(env, str, 0), 1);
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setWidth(
        JNIEnv *env,
        jobject obj,
        jint width) {
    screenWidth = width;
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setHeight(
        JNIEnv *env,
        jobject obj,
        jint height) {
    screenHeight = height;
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setWidthScale(
        JNIEnv *env,
        jobject obj,
        jfloat s) {
    ws = s;
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setHeightScale(
        JNIEnv *env,
        jobject obj,
        jfloat s) {
    hs = s;
}

JNIEXPORT jint JNICALL
Java_org_echoline_drawterm_MainActivity_dtmain(
        JNIEnv *env,
        jobject obj,
        jobjectArray argv) {
    int i, ret;
    char **args = (char **) malloc(((*env)->GetArrayLength(env, argv)+1) * sizeof(char *));

    for (i = 0; i < (*env)->GetArrayLength(env, argv); i++) {
        jobject str = (jobject) (*env)->GetObjectArrayElement(env, argv, i);
        args[i] = strdup((char*)(*env)->GetStringUTFChars(env, (jstring)str, 0));
    }
    args[(*env)->GetArrayLength(env, argv)] = NULL;

    ret = dt_main(i, args);

    for (i = 0; args[i] != NULL; i++) {
        free(args[i]);
    }
    free(args);

    return ret;
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setMouse(
        JNIEnv *env,
        jobject obj,
        jintArray args) {
    jboolean isCopy;
    jint *data;
    if ((*env)->GetArrayLength(env, args) < 3)
        return;
    data = (*env)->GetIntArrayElements(env, args, &isCopy);
    mousept.x = (int)(data[0] / ws);
    mousept.y = (int)(data[1] / hs);
    buttons = data[2];
    (*env)->ReleaseIntArrayElements(env, args, data, 0);
    absmousetrack(mousept.x, mousept.y, buttons, ticks());
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_setDTSurface(
	JNIEnv* jenv,
	jobject obj,
	jobject surface) {
    if (surface != NULL) {
        window = ANativeWindow_fromSurface(jenv, surface);
	ANativeWindow_setBuffersGeometry(window, screenWidth, screenHeight,
		AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM);
	flushmemscreen(Rect(0, 0, screenWidth, screenHeight));
    } else if (window != NULL) {
        ANativeWindow_release(window);
	window = NULL;
    }
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_exitDT(
	JNIEnv* jenv,
	jobject obj) {
    exit(0);
}

JNIEXPORT void JNICALL
Java_org_echoline_drawterm_MainActivity_sendPicture(
	JNIEnv* env,
	jobject obj,
	jbyteArray array) {
    jint len = (*env)->GetArrayLength(env, array);
    jbyte *bytes = (*env)->GetByteArrayElements(env, array, NULL);
    camlen = len;
    cambuf = malloc(camlen);
    memcpy(cambuf, bytes, camlen);
    (*env)->ReleaseByteArrayElements(env, array, bytes, 0);
}


A gui-android/java/org/echoline/drawterm/DrawTermThread.java => gui-android/java/org/echoline/drawterm/DrawTermThread.java +32 -0
@@ 0,0 1,32 @@
package org.echoline.drawterm;

/**
 * Created by eli on 12/4/17.
 */

public class DrawTermThread extends Thread {
	private MainActivity m;
	private String p;
	private String []args;

	public DrawTermThread(String []args, String p, MainActivity m) {
		this.m = m;
		this.p = p;
		this.args = args;
	}

	@Override
	public void run() {
		if (p != null && !p.equals(""))
			m.setPass(p);
		m.dtmain(args);
		m.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				m.exitDT();
				m.setContentView(R.layout.server_main);
				m.populateServers(m);
			}
		});
	}
}

A gui-android/java/org/echoline/drawterm/MainActivity.java => gui-android/java/org/echoline/drawterm/MainActivity.java +437 -0
@@ 0,0 1,437 @@
package org.echoline.drawterm;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Environment;

import android.app.Activity;

import android.app.Notification;
import android.app.NotificationManager;

import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.WindowManager;
import android.view.Surface;
import android.view.inputmethod.InputMethodManager;
import android.view.KeyEvent;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.TotalCaptureResult;
import android.media.Image;
import android.media.ImageReader;
import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.HandlerThread;

import java.io.File;
import java.util.Map;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.ArrayList;

public class MainActivity extends Activity {
	private Map<String, ?> map;
	private MainActivity mainActivity;
	private boolean dtrunning = false;
	private DrawTermThread dthread;
	private int notificationId;
	private CameraDevice cameraDevice = null;
	private byte []jpegBytes;

	static {
		System.loadLibrary("drawterm");
	}

	public void showNotification(String text) {
		Notification.Builder builder = new Notification.Builder(this)
			.setDefaults(Notification.DEFAULT_SOUND)
			.setSmallIcon(R.drawable.ic_small)
			.setContentText(text)
			.setStyle(new Notification.BigTextStyle().bigText(text))
			.setPriority(Notification.PRIORITY_DEFAULT);

		((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE)).notify(notificationId, builder.build());
		notificationId++;
	}

	public int numCameras() {
		try {
			return ((CameraManager)getSystemService(Context.CAMERA_SERVICE)).getCameraIdList().length;
		} catch (CameraAccessException e) {
			Log.w("drawterm", e.toString());
			return 0;
		}
	}

	public void takePicture(int id) {
		try {
			HandlerThread mBackgroundThread = new HandlerThread("Camera Background");
			mBackgroundThread.start();
			Handler mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
			CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
			String []cameraIdList = manager.getCameraIdList();
			manager.openCamera(cameraIdList[id], new CameraDevice.StateCallback() {
				public void onOpened(CameraDevice device) {
					cameraDevice = device;
				}
				public void onDisconnected(CameraDevice device) {
					if (cameraDevice != null)
						cameraDevice.close();
					cameraDevice = null;
				}
				public void onError(CameraDevice device, int error) {
					if (cameraDevice != null)
						cameraDevice.close();
					cameraDevice = null;
				}
			}, mBackgroundHandler);
			ImageReader reader = ImageReader.newInstance(640, 480, ImageFormat.JPEG, 1);
			CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
			captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
			captureBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
			captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
			captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getWindowManager().getDefaultDisplay().getRotation());
			captureBuilder.addTarget(reader.getSurface());
			reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
				public void onImageAvailable(ImageReader reader) {
					Image image = null;
					try {
						image = reader.acquireLatestImage();
						ByteBuffer buffer = image.getPlanes()[0].getBuffer();
						jpegBytes = new byte[buffer.capacity()];
						buffer.get(jpegBytes);
					} catch (Exception e) {
						Log.w("drawterm", e.toString());
					} finally {
						if (image != null) {
							image.close();
						}
					}
				}
			}, mBackgroundHandler);
			List<Surface> outputSurfaces = new ArrayList<Surface>(1);
			outputSurfaces.add(reader.getSurface());
			cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
				public void onConfigured(CameraCaptureSession session) {
					try {
						List<CaptureRequest> captureRequests = new ArrayList<CaptureRequest>(10);
						for (int i = 0; i < 10; i++)
							captureRequests.add(captureBuilder.build());
						session.captureBurst(captureRequests, new CameraCaptureSession.CaptureCallback() {
							public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber) {
								try {
									sendPicture(jpegBytes);
									mBackgroundThread.quitSafely();
									mBackgroundThread.join();
								} catch (Exception e) {
									Log.w("drawterm", e.toString());
								}
							}
						}, mBackgroundHandler);
					} catch (CameraAccessException e) {
						e.printStackTrace();
					}
				}
				public void onConfigureFailed(CameraCaptureSession session) {
				}
			}, mBackgroundHandler);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void serverView(View v) {
		setContentView(R.layout.server_main);
		serverButtons();

		String s = (String)map.get(((TextView)v).getText().toString());
		String []a = s.split("\007");

		((EditText)findViewById(R.id.cpuServer)).setText((String)a[0]);
		((EditText)findViewById(R.id.authServer)).setText((String)a[1]);
		((EditText)findViewById(R.id.userName)).setText((String)a[2]);
		if (a.length > 3)
			((EditText)findViewById(R.id.passWord)).setText((String)a[3]);
	}

	public void populateServers(Context context) {
		ListView ll = (ListView)findViewById(R.id.servers);
		ArrayAdapter<String> la = new ArrayAdapter<String>(this, R.layout.item_main);
		SharedPreferences settings = getSharedPreferences("DrawtermPrefs", 0);
		map = (Map<String, ?>)settings.getAll();
		String key;
		Object []keys = map.keySet().toArray();
		for (int i = 0; i < keys.length; i++) {
			key = (String)keys[i];
			la.add(key);
		}
		ll.setAdapter(la);

		setDTSurface(null);
		dtrunning = false;
	}

	public void runDrawterm(String []args, String pass) {
		Resources res = getResources();
		DisplayMetrics dm = res.getDisplayMetrics();

		int wp = dm.widthPixels;
		int hp = dm.heightPixels;

		setContentView(R.layout.drawterm_main);

		Button kbutton = (Button)findViewById(R.id.keyboardToggle);
		kbutton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(final View view) {
				InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
				imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
			}
		});

		int rid = res.getIdentifier("navigation_bar_height", "dimen", "android");
		if (rid > 0) {
			hp -= res.getDimensionPixelSize(rid);
		}
		LinearLayout ll = (LinearLayout)findViewById(R.id.dtButtons);
		hp -= ll.getHeight();

		int w = (int)(wp * (160.0/dm.xdpi));
		int h = (int)(hp * (160.0/dm.ydpi));
		float ws = (float)wp/w;
		float hs = (float)hp/h;
		// only scale up
		if (ws < 1) {
			ws = 1;
			w = wp;
		}
		if (hs < 1) {
			hs = 1;
			h = hp;
		}

		MySurfaceView mView = new MySurfaceView(mainActivity, w, h, ws, hs);
		mView.getHolder().setFixedSize(w, h);

		LinearLayout l = (LinearLayout)findViewById(R.id.dlayout);
		l.addView(mView, 1, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));

		dthread = new DrawTermThread(args, pass, mainActivity);
		dthread.start();

		dtrunning = true;
	}

	public void serverButtons() {
		Button button = (Button)findViewById(R.id.save);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				String cpu = ((EditText)findViewById(R.id.cpuServer)).getText().toString();
				String auth = ((EditText)findViewById(R.id.authServer)).getText().toString();
				String user = ((EditText)findViewById(R.id.userName)).getText().toString();
				String pass = ((EditText)findViewById(R.id.passWord)).getText().toString();

				SharedPreferences settings = getSharedPreferences("DrawtermPrefs", 0);
				SharedPreferences.Editor editor = settings.edit();
				editor.putString(user + "@" + cpu + " (auth="  + auth + ")", cpu + "\007" + auth + "\007" + user + "\007" + pass);
				editor.commit();
			}
		});

		button = (Button) findViewById(R.id.connect);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(final View view) {
				String cpu = ((EditText)findViewById(R.id.cpuServer)).getText().toString();
				String auth = ((EditText)findViewById(R.id.authServer)).getText().toString();
				String user = ((EditText)findViewById(R.id.userName)).getText().toString();
				String pass = ((EditText)findViewById(R.id.passWord)).getText().toString();

				String args[] = {"drawterm", "-p", "-h", cpu, "-a", auth, "-u", user};
				runDrawterm(args, pass);
			}
		});
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		mainActivity = this;
		setObject();
		setContentView(R.layout.activity_main);
		populateServers(this);

		View fab = findViewById(R.id.fab);
		fab.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				setContentView(R.layout.server_main);
				serverButtons();
			}
		});
	}

	@Override
	public boolean dispatchKeyEvent(KeyEvent event)
	{
		if (!dtrunning) {
			return super.dispatchKeyEvent(event);
		}

		int k = event.getUnicodeChar();
		if (k == 0) {
			k = event.getDisplayLabel();
			if (k >= 'A' && k <= 'Z')
				k |= 0x20;
		}
		String chars = event.getCharacters();
		if (k == 0 && chars != null) {
			for (int i = 0; i < chars.length(); i++) {
				k = chars.codePointAt(i);
				keyDown(k);
				keyUp(k);
			}
			return true;
		}

		if (k == 0) switch (event.getKeyCode()) {
		case KeyEvent.KEYCODE_DEL:
			k = 0x0008;
			break;
		case KeyEvent.KEYCODE_FORWARD_DEL:
			k = 0x007F;
			break;
		case KeyEvent.KEYCODE_ESCAPE:
			k = 0x001B;
			break;
		case KeyEvent.KEYCODE_MOVE_HOME:
			k = 0xF00D;
			break;
		case KeyEvent.KEYCODE_MOVE_END:
			k = 0xF018;
			break;
		case KeyEvent.KEYCODE_PAGE_UP:
			k = 0xF00F;
			break;
		case KeyEvent.KEYCODE_PAGE_DOWN:
			k = 0xF013;
			break;
		case KeyEvent.KEYCODE_INSERT:
			k = 0xF014;
			break;
		case KeyEvent.KEYCODE_SYSRQ:
			k = 0xF010;
			break;
		case KeyEvent.KEYCODE_DPAD_UP:
			k = 0xF00E;
			break;
		case KeyEvent.KEYCODE_DPAD_LEFT:
			k = 0xF011;
			break;
		case KeyEvent.KEYCODE_DPAD_RIGHT:
			k = 0xF012;
			break;
		case KeyEvent.KEYCODE_DPAD_DOWN:
			k = 0xF800;
			break;
		}

		if (k == 0)
			return true;

		if (event.isCtrlPressed()) {
			keyDown(0xF017);
		}
		if (event.isAltPressed() && k < 128) {
			keyDown(0xF015);
		}

		if (event.getAction() == KeyEvent.ACTION_DOWN) {
			keyDown(k);
		}
		else if (event.getAction() == KeyEvent.ACTION_UP) {
			keyUp(k);
		}

		if (event.isCtrlPressed()) {
			keyUp(0xF017);
		}
		if (event.isAltPressed() && k < 128) {
			keyUp(0xF015);
		}

		return true;
	}

	@Override
	public void onBackPressed()
	{
	}

	@Override
	public void onDestroy()
	{
		setDTSurface(null);
		dtrunning = false;
		exitDT();
		super.onDestroy();
	}

	public void setClipBoard(String str) {
		ClipboardManager cm = (ClipboardManager)getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
		if (cm != null) {
			ClipData cd = ClipData.newPlainText(null, str);
			cm.setPrimaryClip(cd);
		}
	}

	public String getClipBoard() {
		ClipboardManager cm = (ClipboardManager)getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
		if (cm != null) {
			ClipData cd = cm.getPrimaryClip();
			if (cd != null)
				return (String)(cd.getItemAt(0).coerceToText(mainActivity.getApplicationContext()).toString());
		}
		return "";
	}

	public native void dtmain(Object[] args);
	public native void setPass(String arg);
	public native void setWidth(int arg);
	public native void setHeight(int arg);
	public native void setWidthScale(float arg);
	public native void setHeightScale(float arg);
	public native void setDTSurface(Surface surface);
	public native void setMouse(int[] args);
	public native void setObject();
	public native void keyDown(int c);
	public native void keyUp(int c);
	public native void exitDT();
	public native void sendPicture(byte[] array);
}

A gui-android/java/org/echoline/drawterm/MySurfaceView.java => gui-android/java/org/echoline/drawterm/MySurfaceView.java +91 -0
@@ 0,0 1,91 @@
package org.echoline.drawterm;

import android.util.Log;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.security.spec.ECField;

/**
 * Created by eli on 12/3/17.
 */
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
	private int screenWidth, screenHeight;
	private MainActivity mainActivity;
	private float ws, hs;

	public MySurfaceView(Context context, int w, int h, float ws, float hs) {
		super(context);
		screenHeight = h;
		screenWidth = w;
		this.ws = ws;
		this.hs = hs;
		mainActivity = (MainActivity)context;
		mainActivity.setWidth(screenWidth);
		mainActivity.setHeight(screenHeight);
		mainActivity.setWidthScale(ws);
		mainActivity.setHeightScale(hs);
		setWillNotDraw(true);

		getHolder().addCallback(this);

		setOnTouchListener(new View.OnTouchListener() {
			private int[] mouse = new int[3];

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				CheckBox left = (CheckBox)mainActivity.findViewById(R.id.mouseLeft);
				CheckBox middle = (CheckBox)mainActivity.findViewById(R.id.mouseMiddle);
				CheckBox right = (CheckBox)mainActivity.findViewById(R.id.mouseRight);
				CheckBox up = (CheckBox)mainActivity.findViewById(R.id.mouseUp);
				CheckBox down = (CheckBox)mainActivity.findViewById(R.id.mouseDown);
				int buttons = (left.isChecked()? 1: 0) |
								(middle.isChecked()? 2: 0) |
								(right.isChecked()? 4: 0) |
								(up.isChecked()? 8: 0) |
								(down.isChecked()? 16: 0);
				if (event.getAction() == MotionEvent.ACTION_DOWN) {
					mouse[0] = Math.round(event.getX());
					mouse[1] = Math.round(event.getY());
					mouse[2] = buttons;
					mainActivity.setMouse(mouse);
				} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
					mouse[0] = Math.round(event.getX());
					mouse[1] = Math.round(event.getY());
					mouse[2] = buttons;
					mainActivity.setMouse(mouse);
				} else if (event.getAction() == MotionEvent.ACTION_UP) {
					mouse[0] = Math.round(event.getX());
					mouse[1] = Math.round(event.getY());
					mouse[2] = 0;
					mainActivity.setMouse(mouse);
				}
				return true;
			}
		});
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mainActivity.setDTSurface(holder.getSurface());
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int w, int h, int format) {
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mainActivity.setDTSurface(null);
	}
}

A gui-android/res/layout/activity_main.xml => gui-android/res/layout/activity_main.xml +24 -0
@@ 0,0 1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<android.widget.FrameLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res-auto"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	tools:context="org.echoline.drawterm.MainActivity">

	<include layout="@layout/content_main" />

	<LinearLayout
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_gravity="bottom|end">

		<Button
			android:id="@+id/fab"
			android:text="add server"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"/>
	</LinearLayout>

</android.widget.FrameLayout>

A gui-android/res/layout/content_main.xml => gui-android/res/layout/content_main.xml +16 -0
@@ 0,0 1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<android.widget.FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="org.echoline.drawterm.MainActivity"
    tools:showIn="@layout/activity_main">
    <ListView
        android:id="@+id/servers"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    </ListView>
</android.widget.FrameLayout>

A gui-android/res/layout/drawterm_main.xml => gui-android/res/layout/drawterm_main.xml +44 -0
@@ 0,0 1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/dlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="org.echoline.drawterm.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:gravity="center_horizontal"
        android:orientation="horizontal"
        android:id="@+id/dtButtons">

        <CheckBox
            android:id="@+id/mouseLeft"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
        <CheckBox
            android:id="@+id/mouseMiddle"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
        <CheckBox
            android:id="@+id/mouseRight"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
        <CheckBox
            android:id="@+id/mouseUp"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
        <CheckBox
            android:id="@+id/mouseDown"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
        <Button
            android:id="@+id/keyboardToggle"
            android:text="kb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

A gui-android/res/layout/item_main.xml => gui-android/res/layout/item_main.xml +8 -0
@@ 0,0 1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:padding="10dp"
    android:textSize="16sp"
    android:onClick="serverView">
</TextView>


A gui-android/res/layout/server_main.xml => gui-android/res/layout/server_main.xml +56 -0
@@ 0,0 1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<android.widget.FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="org.echoline.drawterm.MainActivity">

    <LinearLayout
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:orientation="vertical">

        <EditText
            android:id="@+id/cpuServer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="CPU" />
        <EditText
            android:id="@+id/authServer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="Auth" />
        <EditText
            android:id="@+id/userName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="Username" />

        <EditText
            android:id="@+id/passWord"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:password="true"
            android:hint="Password"/>

        <Button
            android:id="@+id/save"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Save" />

        <Button
            android:id="@+id/connect"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Connect" />

    </LinearLayout>

</android.widget.FrameLayout>

A gui-android/res/values/colors.xml => gui-android/res/values/colors.xml +6 -0
@@ 0,0 1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#3FB551</color>
</resources>

A gui-android/res/values/strings.xml => gui-android/res/values/strings.xml +3 -0
@@ 0,0 1,3 @@
<resources>
    <string name="app_name">Drawterm</string>
</resources>

A gui-android/res/values/styles.xml => gui-android/res/values/styles.xml +8 -0
@@ 0,0 1,8 @@
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="android:Theme.NoTitleBar">
        <!-- Customize your theme here. -->
    </style>

</resources>

A gui-fbdev/Makefile => gui-fbdev/Makefile +12 -0
@@ 0,0 1,12 @@
ROOT=..
include ../Make.config
LIB=libgui.a

OFILES=\
	fbdev.$O\

default: $(LIB)
$(LIB): $(OFILES)
	$(AR) r $(LIB) $(OFILES)
	$(RANLIB) $(LIB)


A gui-fbdev/fbdev.c => gui-fbdev/fbdev.c +673 -0
@@ 0,0 1,673 @@
#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"

#include <draw.h>
#include <memdraw.h>
#include <keyboard.h>
#include <cursor.h>
#include "screen.h"

#undef long
#undef ulong

#include <linux/fb.h>
#include <linux/input.h>

uchar*		fbp;
Memimage*	screenimage;
Memimage*	backbuf;
Rectangle	screenr;
char*		snarfbuf;
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
int		*eventfds = NULL;
int		neventfds;
Point		mousexy;
char		shift_state;
int		ttyfd;
char*		tty;
char		hidden;
int		devicesfd;
ulong		chan;
int		depth;

#include <sys/ioctl.h>
#include <sys/mman.h>
#include <limits.h>

#include <fcntl.h>
#include <termios.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <linux/keyboard.h>
#include <signal.h>

#include <termios.h>

#define ulong p9_ulong

int code2key[] = {
	Kesc, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\x08',
	'\x09', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
	Kctl, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', Kshift,
	'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', Kshift, '*', Kalt,
	' ', Kcaps, KF|1, KF|2, KF|3, KF|4, KF|5, KF|6, KF|7, KF|8, KF|9, KF|10,
	Knum, Kscroll,
	'7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.',
};

int code2key_shift[] = {
	Kesc, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\x08',
	'\x09', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
	Kctl, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '\"', '~', Kshift,
	'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', Kshift, '*', Kalt,
	' ', Kcaps, KF|1, KF|2, KF|3, KF|4, KF|5, KF|6, KF|7, KF|8, KF|9, KF|10,
	Knum, Kscroll,
	'7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.',
};

Memimage *gscreen;
char *snarfbuf = nil;

int onevent(struct input_event*);
void termctl(uint32_t o, int or);
void ctrlc(int sig);

void
_fbput(Memimage *m, Rectangle r) {
	int y;

	for (y = r.min.y; y < r.max.y; y++){
		long loc = y * finfo.line_length + r.min.x * depth;
		void *ptr = m->data->bdata + y * m->width * 4 + r.min.x * depth;

		memcpy(fbp + loc, ptr, Dx(r) * depth);
	}
}

Memimage*
fbattach(int fbdevidx)
{
	Rectangle r;
	char devname[64];
	size_t size;
	int fd;

	/*
	 * Connect to /dev/fb0
	 */
	snprintf(devname, sizeof(devname) - 1, "/dev/fb%d", fbdevidx);
	if ((fd = open(devname, O_RDWR)) < 0)
		goto err;

	if (ioctl(fd, FBIOGET_VSCREENINFO, &(vinfo)) < 0)
		goto err;

	switch (vinfo.bits_per_pixel) {
	case 32:
		chan = XRGB32;
		depth = 4;
		break;
	case 16:
		chan = RGB16;
		depth = 2;
		break;
	default:
		goto err;
	}

	if (ioctl(fd, FBIOGET_FSCREENINFO, &(finfo)) < 0)
		goto err;

	size = vinfo.yres_virtual * finfo.line_length;
	if ((fbp = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)0)) < 0)
		goto err;
	/*
	 * Figure out underlying screen format.
	 */
	r = Rect(0, 0, vinfo.xres_virtual, vinfo.yres_virtual);

	screenr = r;

	screenimage = allocmemimage(r, chan);
	backbuf = allocmemimage(r, chan);
	return backbuf;

err:
	return nil;
}

int
eventattach()
{
	char eventfile[PATH_MAX] = "";
	char line[PATH_MAX];
	FILE *devices;
	char *ptr;

	neventfds = 0;
	devices = fopen("/proc/bus/input/devices", "r");
	if (devices == NULL)
		return -1;
	while (fgets(line, sizeof(line)-1, devices) != NULL)
		if (line[0] == 'H') {
			ptr = strstr(line, "event");
			if (ptr == NULL)
				continue;
			ptr[strcspn(ptr, " \r\n")] = '\0';
			snprintf(eventfile, sizeof(eventfile)-1, "/dev/input/%s", ptr);
			neventfds++;
			eventfds = realloc(eventfds, neventfds * sizeof(int));
			eventfds[neventfds-1] = open(eventfile, O_RDONLY);
			if (eventfds[neventfds-1] < 0)
				neventfds--;
		}
	fclose(devices);

	if (neventfds == 0)
		return -1;
	return 1;
}

void
flushmemscreen(Rectangle r)
{
	int x, y, i;
	Point p;
	long fbloc;
	int x2, y2;

	if (rectclip(&r, screenimage->r) == 0)
		return;
	if (Dx(r) == 0 || Dy(r) == 0)
		return;

	assert(!canqlock(&drawlock));

	memimagedraw(screenimage, r, backbuf, r.min, nil, r.min, S);

	if (hidden != 0)
		return;

	p = mousexy;

	// draw cursor
	for (x = 0; x < 16; x++) {
		x2 = x + cursor.offset.x;

		if ((p.x + x2) < 0)
			continue;

		if ((p.x + x2) >= screenimage->r.max.x)
			break;

		for (y = 0; y < 16; y++) {
			y2 = y + cursor.offset.y;

			if ((p.y + y2) < 0)
				continue;

			if ((p.y + y2) >= screenimage->r.max.y)
				break;

			i = y * 2 + x / 8;
			fbloc = ((p.y+y2) * screenimage->r.max.x + (p.x+x2)) * depth;

			if (cursor.clr[i] & (128 >> (x % 8))) {
				switch (depth) {
				case 2:
					*((uint16_t*)(screenimage->data->bdata + fbloc)) = 0xFFFF;
					break;
				case 4:
					*((uint32_t*)(screenimage->data->bdata + fbloc)) = 0xFFFFFFFF;
					break;
				}
			}

			if (cursor.set[i] & (128 >> (x % 8))) {
				switch (depth) {
				case 2:
					*((uint16_t*)(screenimage->data->bdata + fbloc)) = 0x0000;
					break;
				case 4:
					*((uint32_t*)(screenimage->data->bdata + fbloc)) = 0xFF000000;
					break;
				}
			}
		}
	}

	_fbput(screenimage, r);
}

static void
fbproc(void *v)
{
	struct input_event data;
	char buf[32];
	struct pollfd *pfd;
	int r;
	int ioctlarg;

	pfd = calloc(3, sizeof(struct pollfd));
	pfd[0].fd = ttyfd; // for virtual console switches
	pfd[0].events = POLLPRI;
	pfd[1].fd = 0; // stdin goes to nowhere
	pfd[1].events = POLLIN;
	pfd[2].fd = open("/proc/bus/input/devices", O_RDONLY); // input hotplug
	if (pfd[2].fd < 0)
		panic("cannot open /proc/bus/input/devices: %r");
	pfd[2].events = POLLIN;

TOP:
	while(read(pfd[2].fd, buf, 31) > 0);

	pfd = realloc(pfd, sizeof(struct pollfd) * (neventfds + 3));
	for (r = 0; r < neventfds; r++) {
		pfd[r+3].fd = eventfds[r];
		pfd[r+3].events = POLLIN;
	}

	for(;;) {
		shift_state = 6;
		if (ioctl(0, TIOCLINUX, &shift_state) < 0)
			panic("ioctl TIOCLINUX 6: %r");

		r = poll(pfd, 3+neventfds, -1);
		if (r < 0)
			oserror();
		if (pfd[0].revents & POLLPRI) {
			if ((r = read(ttyfd, buf, 31)) <= 0)
				panic("ttyfd read: %r");
			buf[r] = '\0';
			if (strcmp(buf, tty) == 0) {
				hidden = 0;
				printf("\e[?25l");
				fflush(stdout);
				qlock(&drawlock);
				flushmemscreen(gscreen->clipr);
				qunlock(&drawlock);
			}
			else
				hidden = 1;
			close(ttyfd);
			ttyfd = open("/sys/class/tty/tty0/active", O_RDONLY);
			if (ttyfd < 0)
				panic("cannot open tty active fd: %r");
			pfd[0].fd = ttyfd;
			read(ttyfd, buf, 0);
		}
		if (pfd[1].revents & POLLIN)
			read(pfd[1].fd, buf, 31);
		if (pfd[2].revents & POLLIN) {
			for (r = 0; r < neventfds; r++)
				close(eventfds[r]);
			if(eventattach() < 0) {
				panic("cannot open event files: %r");
			}
			goto TOP;
		}
		for (r = 0; r < neventfds; r++)
			if (pfd[r+3].revents & POLLIN) {
				if (read(pfd[r+3].fd, &data, sizeof(data)) != sizeof(data))
					panic("eventfd read: %r");
				if (onevent(&data) == 0) {
					ioctlarg = 15;
					if (ioctl(0, TIOCLINUX, &ioctlarg) != 0) {
						ioctlarg = 4;
						ioctl(0, TIOCLINUX, &ioctlarg);
						qlock(&drawlock);
						flushmemscreen(gscreen->clipr);
						qunlock(&drawlock);
					} else {
						write(1, "\033[9;30]", 7);
					}
				}
			}
	}

	printf("\e[?25h");
	fflush(stdout);
	termctl(ECHO, 1);
	free(pfd);
}

void
screensize(Rectangle r, ulong chan)
{
	gscreen = backbuf;
	gscreen->clipr = ZR;
}

void
screeninit(void)
{
	int r;
	char buf[1];

	// set up terminal
	printf("\e[?25l");
	fflush(stdout);
	termctl(~(ICANON|ECHO), 0);
	signal(SIGINT, ctrlc);

	memimageinit();

	// tty switching
	ttyfd = open("/sys/class/tty/tty0/active", O_RDONLY);
	if (ttyfd >= 0) {
		tty = malloc(32);
		r = read(ttyfd, tty, 31);
		if (r >= 0)
			tty[r] = '\0';
		else
			tty[0] = '\0';
		close(ttyfd);
		ttyfd = open("/sys/class/tty/tty0/active", O_RDONLY);
	}
	if (ttyfd < 0)
		panic("cannot open tty active fd: %r");
	read(ttyfd, buf, 0);
	hidden = 0;

	if(fbattach(0) == nil) {
		panic("cannot open framebuffer: %r");
	}

	if(eventattach() < 0) {
		panic("cannot open event files: %r");
	}

	screensize(screenr, chan);
	if (gscreen == nil)
		panic("screensize failed");

	gscreen->clipr = screenr;
	kproc("fbdev", fbproc, nil);

	terminit();

	qlock(&drawlock);
	flushmemscreen(gscreen->clipr);
	qunlock(&drawlock);
}

Memdata*
attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
{
	*r = gscreen->clipr;
	*chan = gscreen->chan;
	*depth = gscreen->depth;
	*width = gscreen->width;
	*softscreen = 1;

	gscreen->data->ref++;
	return gscreen->data;
}

void
getcolor(ulong i, ulong *r, ulong *g, ulong *b)
{
	ulong v;
	
	v = cmap2rgb(i);
	*r = (v>>16)&0xFF;
	*g = (v>>8)&0xFF;
	*b = v&0xFF;
}

void
setcolor(ulong i, ulong r, ulong g, ulong b)
{
	/* no-op */
	return;
}

char*
clipread(void)
{
	if(snarfbuf)
		return strdup(snarfbuf);
	return nil;
}

int
clipwrite(char *buf)
{
	if(snarfbuf)
		free(snarfbuf);
	snarfbuf = strdup(buf);
	return 0;
}

void
guimain(void)
{
	cpubody();
}

void
termctl(uint32_t o, int or)
{
	struct termios t;

	tcgetattr(0, &t);
	if (or)
		t.c_lflag |= o;
	else
		t.c_lflag &= o;
	tcsetattr(0, TCSANOW, &t);
}

void
ctrlc(int sig) {
}

int
onevent(struct input_event *data)
{
	Rectangle old, new;
	ulong msec;
	static int buttons;
	static Point coord;
	static char touched;
	static Point startmousept;
	static Point startpt;
	int key;
	static ulong lastmsec = 0;

	if (hidden != 0)
		return -1;

	msec = ticks();

	old.min = mousexy;
	old.max = addpt(old.min, Pt(16, 16));

	buttons &= ~0x18;

	switch(data->type) {
	case 3:
		switch(data->code) {
		case 0:
			coord.x = data->value;
			break;
		case 1:
			coord.y = data->value;
			break;
		case 0x18:
		case 0x1c:
			if (data->value == 0)
				touched = 0;
			else if (data->value > 24) {
				touched = 1;
				startmousept = coord;
				startpt = mousexy;
			}
			break;
		default:
			return -1;
		}
		if (touched)
			mousexy = addpt(startpt, divpt(subpt(coord, startmousept), 4));
		break;
	case 2:
		switch(data->code) {
		case 0:
			mousexy.x += data->value;
			break;
		case 1:
			mousexy.y += data->value;
			break;
		case 8:
			buttons |= data->value == 1? 8: 16;
			break;
		default:
			return -1;
		}
		break;
	case 1:
		switch(data->code) {
		case 0x110:
			if (data->value == 1)
				buttons |= 1;
			else
				buttons &= ~1;
			break;
		case 0x111:
			if (data->value == 1)
				buttons |= shift_state & (1 << KG_SHIFT)? 2: 4;
			else
				buttons &= ~(shift_state & (1 << KG_SHIFT)? 2: 4);
			break;
		case 0x112:
			if (data->value == 1)
				buttons |= 2;
			else
				buttons &= ~2;
			break;
		default:
			if (hidden)
				return 0;
			if (data->code > 0 && data->code <= nelem(code2key)) {
				if (shift_state & (1 << KG_SHIFT))
					key = code2key_shift[data->code-1];
				else
					key = code2key[data->code-1];
				if (key == Kshift)
					return -1;
				kbdkey(key, data->value);
				return 0;
			}
			switch(data->code) {
			case 87:
				kbdkey(KF|11, data->value);
				break;
			case 88:
				kbdkey(KF|12, data->value);
				break;
			case 96:
				kbdkey('\n', data->value);
				break;
			case 97:
				kbdkey(Kctl, data->value);
				break;
			case 98:
				kbdkey('/', data->value);
				break;
			case 100:
				kbdkey(Kalt, data->value);
				break;
			case 102:
				kbdkey(Khome, data->value);
				break;
			case 103:
				kbdkey(Kup, data->value);
				break;
			case 104:
				kbdkey(Kpgup, data->value);
				break;
			case 105:
				kbdkey(Kleft, data->value);
				break;
			case 106:
				kbdkey(Kright, data->value);
				break;
			case 107:
				kbdkey(Kend, data->value);
				break;
			case 108:
				kbdkey(Kdown, data->value);
				break;
			case 109:
				kbdkey(Kpgdown, data->value);
				break;
			case 110:
				kbdkey(Kins, data->value);
				break;
			case 111:
				kbdkey(Kdel, data->value);
				break;
			}
			return 0;
		}
		break;
	default:
		return -1;
	}

	if (mousexy.x < screenimage->r.min.x)
		mousexy.x = screenimage->r.min.x;
	if (mousexy.y < screenimage->r.min.y)
		mousexy.y = screenimage->r.min.y;
	if (mousexy.x > screenimage->r.max.x)
		mousexy.x = screenimage->r.max.x;
	if (mousexy.y > screenimage->r.max.y)
		mousexy.y = screenimage->r.max.y;
	
	new.min = mousexy;
	new.max = addpt(new.min, Pt(16, 16)); // size of cursor bitmap

	combinerect(&new, old);
	new.min = subpt(new.min, Pt(16, 16)); // to encompass any cursor->offset

	qlock(&drawlock);
	flushmemscreen(new);
	qunlock(&drawlock);

	if ((msec - lastmsec) < 10)
		if (data->type != 1)
			return 0;

	lastmsec = msec;

	absmousetrack(mousexy.x, mousexy.y, buttons, msec);

	return 0;
}

void
mouseset(Point p)
{
	qlock(&drawlock);
	mousexy = p;
	flushmemscreen(screenr);
	qunlock(&drawlock);
}

void
setcursor(void)
{
	qlock(&drawlock);
	flushmemscreen(screenr);
	qunlock(&drawlock);
}

void
titlewrite(char* buf)
{
}


A kern/devaudio-alsa.c => kern/devaudio-alsa.c +108 -0
@@ 0,0 1,108 @@
/*
 * ALSA
 */
#include <alsa/asoundlib.h>
#include	"u.h"
#include	"lib.h"
#include	"dat.h"
#include	"fns.h"
#include	"error.h"
#include	"devaudio.h"

enum
{
	Channels = 2,
	Rate = 44100,
	Bits = 16,
};

static snd_pcm_t *playback;
static snd_pcm_t *capture;
static int speed = Rate;

/* maybe this should return -1 instead of sysfatal */
void
audiodevopen(void)
{
	if(snd_pcm_open(&playback, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0)
		error("snd_pcm_open playback");

	if(snd_pcm_set_params(playback, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, speed, 1, 500000) < 0)
		error("snd_pcm_set_params playback");

	if(snd_pcm_prepare(playback) < 0)
		error("snd_pcm_prepare playback");

	if(snd_pcm_open(&capture, "default", SND_PCM_STREAM_CAPTURE, 0) < 0)
		error("snd_pcm_open capture");

	if(snd_pcm_set_params(capture, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, speed, 1, 500000) < 0)
		error("snd_pcm_set_params capture");

	if(snd_pcm_prepare(capture) < 0)
		error("snd_pcm_prepare capture");
}

void
audiodevclose(void)
{
	snd_pcm_drain(playback);
	snd_pcm_close(playback);

	snd_pcm_close(capture);
}

void
audiodevsetvol(int what, int left, int right)
{
	if(what == Vspeed){
		speed = left;
		return;
	}
}

void
audiodevgetvol(int what, int *left, int *right)
{
	if(what == Vspeed){
		*left = *right = speed;
		return;
	}

	*left = *right = 100;
}

int
audiodevwrite(void *v, int n)
{
	snd_pcm_sframes_t frames;
	int tot, m;

	for(tot = 0; tot < n; tot += m){
		do {
			frames = snd_pcm_writei(playback, v+tot, (n-tot)/4);
		} while(frames == -EAGAIN);
		if (frames < 0)
			frames = snd_pcm_recover(playback, frames, 0);
		if (frames < 0)
			error((char*)snd_strerror(frames));
		m = frames*4;
	}

	return tot;
}

int
audiodevread(void *v, int n)
{
	snd_pcm_sframes_t frames;

	do {
		frames = snd_pcm_readi(capture, v, n/4);
	} while(frames == -EAGAIN);

	if (frames < 0)
		error((char*)snd_strerror(frames));

	return frames*4;
}

M main.c => main.c +1 -0
@@ 54,6 54,7 @@ main(int argc, char **argv)
	if(bind("#U", "/root", MREPL) < 0)
		panic("bind #U: %r");
	bind("#A", "/dev", MAFTER);
	bind("#N", "/dev", MAFTER);
	bind("#C", "/", MAFTER);

	if(open("/dev/cons", OREAD) != 0)