Skip to content
Snippets Groups Projects
main.cpp 13.8 KiB
Newer Older
#define LOG_TAG "VNCFlinger"
#include <utils/Log.h>
Nick's avatar
Nick committed
#include <jni.h>
#include <gui/Surface.h>
#include <android/native_window_jni.h>
#include <android_view_PointerIcon.h>

#include <fcntl.h>
maxwen's avatar
maxwen committed
#include <fstream>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <inttypes.h>

#include "AndroidDesktop.h"
#include "AndroidSocket.h"
Steve Kondik's avatar
Steve Kondik committed
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>

#include <network/Socket.h>
#include <network/TcpSocket.h>
Nick's avatar
Nick committed
#include <network/UnixSocket.h>
#include <rfb/Configuration.h>
#include <rfb/LogWriter.h>
#include <rfb/Logger_android.h>
#include <rfb/VNCServerST.h>
#include <rfb/util.h>

#include <cutils/properties.h>


using namespace vncflinger;
Steve Kondik's avatar
Steve Kondik committed
using namespace android;

static bool gCaughtSignal = false;
maxwen's avatar
maxwen committed
static std::string mPidFile;
static char gSerialNo[PROPERTY_VALUE_MAX];

static rfb::IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol", 5900);
static rfb::BoolParameter localhostOnly("localhost", "Only allow connections from localhost", false);
Nick's avatar
Nick committed
static rfb::BoolParameter rfbunixandroid("rfbunixandroid", "Use android control socket to create UNIX socket", true);
static rfb::StringParameter rfbunixpath("rfbunixpath", "Unix socket to listen for RFB protocol", "");
static rfb::IntParameter rfbunixmode("rfbunixmode", "Unix socket access mode", 0600);
Nick's avatar
Nick committed
static sp<AndroidDesktop> desktop = NULL;
static JNIEnv* gEnv;
static jobject gThiz;
Erfan Abdi's avatar
Erfan Abdi committed
static jmethodID gMethodNewSurfaceAvailable;
static jmethodID gMethodResizeDisplay;
Erfan Abdi's avatar
Erfan Abdi committed
static jmethodID gMethodSetClipboard;
static jmethodID gMethodGetClipboard;
Nick's avatar
Nick committed

static void printVersion(FILE* fp) {
    fprintf(fp, "VNCFlinger 1.0");
maxwen's avatar
maxwen committed
static void CleanupSignalHandler(int)
{
    ALOGI("You killed me - cleaning up");
Nick's avatar
Nick committed
    desktop = NULL;
maxwen's avatar
maxwen committed
    if (mPidFile.length() != 0) {
        remove(mPidFile.c_str());
    }
    exit(1);
}

Erfan Abdi's avatar
Erfan Abdi committed
void runJniCallbackNewSurfaceAvailable() {
    gEnv->CallVoidMethod(gThiz, gMethodNewSurfaceAvailable);
Erfan Abdi's avatar
Erfan Abdi committed
void runJniCallbackResizeDisplay(int32_t width, int32_t height) {
    gEnv->CallVoidMethod(gThiz, gMethodResizeDisplay, width, height);
Nick's avatar
Nick committed
}

Erfan Abdi's avatar
Erfan Abdi committed
void runJniCallbackSetClipboard(const char* text) {
    jstring jtext = gEnv->NewStringUTF(text);
    gEnv->CallVoidMethod(gThiz, gMethodSetClipboard, jtext);
    gEnv->DeleteLocalRef(jtext);
}

Erfan Abdi's avatar
Erfan Abdi committed
const char* runJniCallbackGetClipboard() {
Erfan Abdi's avatar
Erfan Abdi committed
    jstring jtext = (jstring)gEnv->CallObjectMethod(gThiz, gMethodGetClipboard);
    const char* text = gEnv->GetStringUTFChars(jtext, NULL);
    char* result = strdup(text);
Erfan Abdi's avatar
Erfan Abdi committed
    gEnv->ReleaseStringUTFChars(jtext, text);
    gEnv->DeleteLocalRef(jtext);
    return result;
}

Erfan Abdi's avatar
Erfan Abdi committed
int desktopSetup(int argc, char** argv);
int startService();
Nick's avatar
Nick committed

Erfan Abdi's avatar
Erfan Abdi committed
extern "C" void Java_com_libremobileos_vncflinger_VncFlinger_notifyServerCursorChanged(
    JNIEnv* env, jobject thiz, jobject pointerIconObj) {
    PointerIcon pointerIcon;

    if (desktop != NULL) {
        status_t result = android_view_PointerIcon_getLoadedIcon(env, pointerIconObj, &pointerIcon);
        if (result) {
            ALOGE("Failed to load pointer icon.");
            return;
        }
        if (!pointerIcon.bitmap.isValid()) {
            ALOGE("Pointer icon bitmap not valid");
            return;
        }
        AndroidBitmapInfo bitmapInfo = pointerIcon.bitmap.getInfo();

Nick's avatar
Nick committed
        desktop->setCursor(bitmapInfo.width, bitmapInfo.height, pointerIcon.hotSpotX,
Nick's avatar
Nick committed
                           pointerIcon.hotSpotY, (uint8_t*)pointerIcon.bitmap.getPixels());
Nick's avatar
Nick committed
extern "C" void Java_com_libremobileos_vncflinger_VncFlinger_notifyServerCaptureChanged(
    JNIEnv* env, jboolean enabled) {
    if (desktop != NULL) {
        desktop->capture = enabled;
    }
    return;
}

Erfan Abdi's avatar
Erfan Abdi committed
extern "C" jint Java_com_libremobileos_vncflinger_VncFlinger_initializeVncFlinger(JNIEnv *env,
Nick's avatar
Nick committed
                                                                                   jobject thiz,
Nick's avatar
Nick committed
                                                                                   jobjectArray command_line_args) {
    const int argc = env->GetArrayLength(command_line_args);
    char* argv[argc];
    for (int i=0; i<argc; i++) {
        jstring o = (jstring)(env->GetObjectArrayElement(command_line_args, i));
        const char *cmdline_temp = env->GetStringUTFChars(o, NULL);
        argv[i] = strdup(cmdline_temp);
        env->ReleaseStringUTFChars(o, cmdline_temp);
        env->DeleteLocalRef(o);
    }
    env->DeleteLocalRef(command_line_args);
    gThiz = thiz; gEnv = env;
Erfan Abdi's avatar
Erfan Abdi committed
    gMethodNewSurfaceAvailable =
        env->GetMethodID(env->GetObjectClass(thiz), "onNewSurfaceAvailable", "()V");
    gMethodResizeDisplay = env->GetMethodID(env->GetObjectClass(thiz), "onResizeDisplay", "(II)V");
    gMethodSetClipboard = env->GetMethodID(env->GetObjectClass(thiz), "setServerClipboard", "(Ljava/lang/String;)V");
Erfan Abdi's avatar
Erfan Abdi committed
    gMethodGetClipboard = env->GetMethodID(env->GetObjectClass(thiz), "getServerClipboard", "()Ljava/lang/String;");
Nick's avatar
Nick committed
    return desktopSetup(argc, argv);
Nick's avatar
Nick committed
}
Erfan Abdi's avatar
Erfan Abdi committed
extern "C" jobject Java_com_libremobileos_vncflinger_VncFlinger_getSurface(JNIEnv * env,
Nick's avatar
Nick committed
                                                                            jobject thiz
Nick's avatar
Nick committed
) {
Nick's avatar
Nick committed
    if (desktop == NULL) {
        ALOGV("getSurface: desktop == NULL");
        return NULL;
    }
    if (desktop->mVirtualDisplay == NULL){
        ALOGW("getSurface: mVirtualDisplay == NULL");
        return NULL;
    }
    if (desktop->mVirtualDisplay->getProducer() == NULL){
        ALOGW("getSurface: getProducer() == NULL");
        return NULL;
    }
    ANativeWindow* w = new Surface(desktop->mVirtualDisplay->getProducer(), true);
    //Rect dr = desktop->mVirtualDisplay->getDisplayRect();
    //if we want to bring back window resizing without display resize, we need to scale buffer to dr
    if (w == NULL) {
        ALOGE("getSurface: w == NULL");
        return NULL;
    }
    jobject a = ANativeWindow_toSurface(env, w);
    if (a == NULL) {
        ALOGE("getSurface: a == NULL");
    }
    return a;
Nick's avatar
Nick committed
}
Erfan Abdi's avatar
Erfan Abdi committed
extern "C" jint Java_com_libremobileos_vncflinger_VncFlinger_startService(JNIEnv* env, jobject thiz) {
Nick's avatar
Nick committed
    gThiz = thiz; gEnv = env;
Erfan Abdi's avatar
Erfan Abdi committed
    return startService();
Nick's avatar
Nick committed
}
Erfan Abdi's avatar
Erfan Abdi committed
extern "C" void Java_com_libremobileos_vncflinger_VncFlinger_quit(JNIEnv *env, jobject thiz) {
Nick's avatar
Nick committed
    gCaughtSignal = true;
Nick's avatar
Nick committed
}
maxwen's avatar
maxwen committed

Erfan Abdi's avatar
Erfan Abdi committed
extern "C" void Java_com_libremobileos_vncflinger_VncFlinger_setDisplayProps(JNIEnv *env,
Nick's avatar
Nick committed
                                                                              jobject thiz, jint w,
Nick's avatar
Nick committed
                                                                              jint h, jint rotation, jint layerId, jboolean touch,
Nick's avatar
Nick committed
                                                                              jboolean relative, jboolean clipboard) {
Nick's avatar
Nick committed
    if (desktop == NULL) {
        ALOGW("setDisplayProps: desktop == NULL");
        return;
    }
    std::lock_guard<std::mutex> lock(desktop->jniConfigMutex);
Nick's avatar
Nick committed
    desktop->_width = w; desktop->_height = h; desktop->_rotation = rotation; desktop->mLayerId = layerId; desktop->touch = touch; desktop->relative = relative; desktop->clipboard = clipboard;
Nick's avatar
Nick committed
}

Erfan Abdi's avatar
Erfan Abdi committed
extern "C" void Java_com_libremobileos_vncflinger_VncFlinger_notifyServerClipboardChanged(
Erfan Abdi's avatar
Erfan Abdi committed
    JNIEnv* env, jobject thiz) {
    if (desktop == NULL) {
        ALOGW("notifyClipboardChanged: desktop == NULL");
        return;
    }
    desktop->notifyClipboardChanged();
}

extern "C" void Java_com_libremobileos_vncflinger_VncFlinger_notifyDisplayReady(
    JNIEnv* env, jobject thiz) {
    if (desktop == NULL) {
        ALOGW("notifyDisplayReady: desktop == NULL");
        return;
    }
    desktop->notifyInputChanged();
}

Erfan Abdi's avatar
Erfan Abdi committed
int desktopSetup(int argc, char** argv) {
Nick's avatar
Nick committed
    rfb::initAndroidLogger();
    rfb::LogWriter::setLogParams("*:android:30");
Nick's avatar
Nick committed
    rfb::Configuration::enableServerParams();
Nick's avatar
Nick committed

#ifdef SIGHUP
Nick's avatar
Nick committed
    signal(SIGHUP, CleanupSignalHandler);
Nick's avatar
Nick committed
#endif
Nick's avatar
Nick committed
    signal(SIGINT, CleanupSignalHandler);
    signal(SIGTERM, CleanupSignalHandler);

    for (int i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            if (i + 1 < argc) {
                if (rfb::Configuration::setParam(&argv[i][1], argv[i + 1])) {
                    i++;
                    continue;
                }
            }
        }

        if (rfb::Configuration::setParam(argv[i])) continue;

        if (argv[i][0] == '-') {
            if (strcmp(argv[i], "-pid") == 0) {
                if (i + 1 < argc) {
                    mPidFile = std::string(argv[i + 1]);
                    i++;
                    continue;
                }
            }
            if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "-version") == 0 ||
                strcmp(argv[i], "--version") == 0) {
                printVersion(stdout);
                return 4;
            }
            return 2;
        }

        ALOGE("Invalid input. i=%d", i);
        return 5;
    }

    desktop = new AndroidDesktop();

    return 0;
Nick's avatar
Nick committed
}
Erfan Abdi's avatar
Erfan Abdi committed

int startService() {
Nick's avatar
Nick committed
    property_get("ro.build.product", gSerialNo, "");
    std::string desktopName = "VNCFlinger";
    desktopName += " @ ";
    desktopName += (const char *) gSerialNo;
Steve Kondik's avatar
Steve Kondik committed
    sp<ProcessState> self = ProcessState::self();
    self->startThreadPool();

    std::list<network::SocketListener*> listeners;
maxwen's avatar
maxwen committed
    int ret = 0;
        rfb::VNCServerST server(desktopName.c_str(), desktop.get());

        if (rfbunixpath.getValueStr()[0] != '\0') {
Nick's avatar
Nick committed
            if (rfbunixandroid) {
                listeners.push_back(new AndroidListener(rfbunixpath));
            } else {
                if (rfbunixpath.getValueStr()[0] != '@') {
                    listeners.push_back(new network::UnixListener(rfbunixpath, rfbunixmode));
                } else {
                    listeners.push_back(new AbsUnixListener(rfbunixpath));
                }
            }
            ALOGI("Listening on %s (mode %04o)", (const char*)rfbunixpath, (int)rfbunixmode);
        } else {
            if (localhostOnly) {
                network::createLocalTcpListeners(&listeners, (int)rfbport);
            } else {
                network::createTcpListeners(&listeners, 0, (int)rfbport);
                ALOGI("Listening on port %d", (int)rfbport);
            }
        }

        int eventFd = desktop->getEventFd();
        fcntl(eventFd, F_SETFL, O_NONBLOCK);

maxwen's avatar
maxwen committed
        if (mPidFile.length() != 0) {
            // write a pid file
            ALOGI("pid file %s", mPidFile.c_str());
            pid_t pid = getpid();
            std::ofstream outfile(mPidFile);
            outfile << pid;
            outfile.close();
        }

        while (!gCaughtSignal) {
            int wait_ms;
            struct timeval tv;
            fd_set rfds, wfds;
            std::list<network::Socket*> sockets;
            std::list<network::Socket*>::iterator i;

            FD_ZERO(&rfds);
            FD_ZERO(&wfds);

            FD_SET(eventFd, &rfds);
            for (std::list<network::SocketListener*>::iterator i = listeners.begin();
                 i != listeners.end(); i++)
                FD_SET((*i)->getFd(), &rfds);

            server.getSockets(&sockets);
            int clients_connected = 0;
            for (i = sockets.begin(); i != sockets.end(); i++) {
                if ((*i)->isShutdown()) {
                    server.removeSocket(*i);
                    delete (*i);
                } else {
                    FD_SET((*i)->getFd(), &rfds);
Nick's avatar
Nick committed
                    if ((*i)->outStream().hasBufferedData()) {
                        FD_SET((*i)->getFd(), &wfds);
                    }
                    clients_connected++;
            wait_ms = 0;
            rfb::soonestTimeout(&wait_ms, rfb::Timer::checkTimeouts());
            tv.tv_sec = wait_ms / 1000;
            tv.tv_usec = (wait_ms % 1000) * 1000;
            int n = select(FD_SETSIZE, &rfds, &wfds, 0, wait_ms ? &tv : NULL);
            if (n < 0) {
                if (errno == EINTR) {
                    ALOGV("Interrupted select() system call");
                    continue;
                } else {
                    throw rdr::SystemException("select", errno);
                }
            }

            // Accept new VNC connections
            for (std::list<network::SocketListener*>::iterator i = listeners.begin();
                 i != listeners.end(); i++) {
                if (FD_ISSET((*i)->getFd(), &rfds)) {
                    network::Socket* sock = (*i)->accept();
                    if (sock) {
                        server.addSocket(sock);
                    } else {
                        ALOGW("Client connection rejected");
                    }
                }
            }
            rfb::Timer::checkTimeouts();
            // Client list could have been changed.
            server.getSockets(&sockets);
            // Nothing more to do if there are no client connections.
            if (sockets.empty()) continue;
            // Process events on existing VNC connections
            for (i = sockets.begin(); i != sockets.end(); i++) {
                if (FD_ISSET((*i)->getFd(), &rfds)) server.processSocketReadEvent(*i);
                if (FD_ISSET((*i)->getFd(), &wfds)) server.processSocketWriteEvent(*i);
            }
Nick's avatar
Nick committed
            // Process events from the display
            uint64_t eventVal;
            int status = read(eventFd, &eventVal, sizeof(eventVal));
            if (status > 0 && eventVal > 0) {
                //ALOGV("status=%d eventval=%" PRIu64, status, eventVal);
Nick's avatar
Nick committed
                desktop->processCursor();
                desktop->processInputChanged();
                desktop->processFrames();
Nick's avatar
Nick committed
                desktop->processClipboard();
maxwen's avatar
maxwen committed
        ret = 0;
    } catch (rdr::Exception& e) {
        ALOGE("%s", e.str());
Nick's avatar
Nick committed
        ret = 3;
maxwen's avatar
maxwen committed
    }
Nick's avatar
Nick committed
    desktop = NULL;
maxwen's avatar
maxwen committed
    ALOGI("Bye - cleaning up");
Nick's avatar
Nick committed
    gEnv = NULL;
    gThiz = NULL;
    gSerialNo[0] = '\0';
    for (std::list<network::SocketListener*>::iterator i = listeners.begin();
         i != listeners.end(); i++)
        delete (*i);
maxwen's avatar
maxwen committed
    if (mPidFile.length() != 0) {
        remove(mPidFile.c_str());
maxwen's avatar
maxwen committed
    return ret;