├── MODULE_LICENSE_GPL ├── TODO ├── sepolicy ├── file_contexts └── vncflinger.te ├── .clang-format ├── aidl └── org │ └── chemlab │ └── IVNCService.aidl ├── etc └── vncflinger.rc ├── src ├── AndroidSocket.h ├── AndroidSocket.cpp ├── VNCService.h ├── VirtualDisplay.h ├── InputDevice.h ├── AndroidDesktop.h ├── AndroidPixelBuffer.h ├── VirtualDisplay.cpp ├── AndroidPixelBuffer.cpp ├── AndroidDesktop.cpp ├── main.cpp └── InputDevice.cpp ├── tools ├── header.txt └── update-headers ├── .ycm_extra_conf.py ├── Android.mk └── NOTICE /MODULE_LICENSE_GPL: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Various things that need done: 2 | 3 | Binder interface 4 | Copy/paste 5 | -------------------------------------------------------------------------------- /sepolicy/file_contexts: -------------------------------------------------------------------------------- 1 | /system/bin/vncflinger u:object_r:vncflinger_exec:s0 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | AllowShortBlocksOnASingleLine: false 3 | AllowShortFunctionsOnASingleLine: false 4 | 5 | AccessModifierOffset: -2 6 | ColumnLimit: 100 7 | CommentPragmas: NOLINT:.* 8 | DerivePointerAlignment: false 9 | IndentWidth: 4 10 | PointerAlignment: Left 11 | TabWidth: 4 12 | UseTab: Never 13 | PenaltyExcessCharacter: 32 14 | -------------------------------------------------------------------------------- /aidl/org/chemlab/IVNCService.aidl: -------------------------------------------------------------------------------- 1 | package org.chemlab; 2 | 3 | interface IVNCService { 4 | boolean start(); 5 | boolean stop(); 6 | 7 | boolean setPort(int port); 8 | boolean setV4Address(String addr); 9 | boolean setV6Address(String addr); 10 | 11 | boolean setPassword(String password); 12 | boolean clearPassword(); 13 | } 14 | -------------------------------------------------------------------------------- /etc/vncflinger.rc: -------------------------------------------------------------------------------- 1 | service vncflinger /system/bin/vncflinger -rfbunixpath /dev/socket/vncflinger -SecurityTypes=None 2 | class late_start 3 | disabled 4 | user system 5 | group system input inet readproc 6 | socket vncflinger stream 0666 root system 7 | 8 | on property:persist.vnc.enable=true 9 | start vncflinger 10 | 11 | on property:persist.vnc.enable=false 12 | stop vncflinger 13 | -------------------------------------------------------------------------------- /src/AndroidSocket.h: -------------------------------------------------------------------------------- 1 | #ifndef __ANDROID_SOCKET_H__ 2 | #define __ANDROID_SOCKET_H__ 3 | 4 | #include 5 | 6 | namespace vncflinger { 7 | 8 | class AndroidListener : public network::SocketListener { 9 | public: 10 | AndroidListener(const char *name); 11 | virtual ~AndroidListener(); 12 | 13 | int getMyPort(); 14 | 15 | protected: 16 | virtual network::Socket* createSocket(int fd); 17 | }; 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/AndroidSocket.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace vncflinger; 6 | 7 | AndroidListener::AndroidListener(const char *path) { 8 | 9 | fd = android_get_control_socket(path); 10 | if (fd < 0) { 11 | throw network::SocketException("unable to get Android control socket", EADDRNOTAVAIL); 12 | } 13 | 14 | listen(fd); 15 | } 16 | 17 | AndroidListener::~AndroidListener() 18 | { 19 | } 20 | 21 | network::Socket* AndroidListener::createSocket(int fd) { 22 | return new network::UnixSocket(fd); 23 | } 24 | 25 | int AndroidListener::getMyPort() { 26 | return 0; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /sepolicy/vncflinger.te: -------------------------------------------------------------------------------- 1 | type vncflinger_exec, exec_type, file_type; 2 | type vncflinger, domain; 3 | 4 | init_daemon_domain(vncflinger) 5 | binder_use(vncflinger) 6 | net_domain(vncflinger); 7 | 8 | # uinput 9 | allow vncflinger uhid_device:chr_file rw_file_perms; 10 | 11 | # read buffers from surfaceflinger 12 | allow vncflinger ion_device:chr_file r_file_perms; 13 | allow vncflinger surfaceflinger_service:service_manager find; 14 | binder_call(vncflinger, surfaceflinger); 15 | 16 | # buffer callbacks 17 | binder_call(surfaceflinger, vncflinger); 18 | 19 | # socket 20 | allow vncflinger rootfs:lnk_file getattr; 21 | 22 | # gpu access (needed on rk) 23 | allow vncflinger gpu_device:chr_file { ioctl open read write }; 24 | -------------------------------------------------------------------------------- /tools/header.txt: -------------------------------------------------------------------------------- 1 | 2 | vncflinger - Copyright (C) 2021 Stefanie Kondik 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | 17 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | # Vim YouCompleteMe completion configuration. 2 | # 3 | # See doc/topics/ycm.md for details. 4 | 5 | import os 6 | 7 | repo_root = os.path.dirname(os.path.abspath(__file__)) 8 | 9 | # Paths in the compilation flags must be absolute to allow ycm to find them from 10 | # any working directory. 11 | def AbsolutePath(path): 12 | return os.path.join(repo_root, path) 13 | 14 | flags = [ 15 | '-I', AbsolutePath('src'), 16 | '-I', AbsolutePath('../tigervnc/common'), 17 | '-I', AbsolutePath('../libvncserver'), 18 | '-I', AbsolutePath('../../system/core/include'), 19 | '-I', AbsolutePath('../../frameworks/native/include'), 20 | '-Wall', 21 | '-Werror', 22 | '-Wextra', 23 | '-pedantic', 24 | '-Wno-newline-eof', 25 | '-Wwrite-strings', 26 | '-std=c++11', 27 | '-x', 'c++' 28 | ] 29 | 30 | def FlagsForFile(filename, **kwargs): 31 | return { 32 | 'flags': flags, 33 | 'do_cache': True 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_SRC_FILES := \ 6 | src/AndroidDesktop.cpp \ 7 | src/AndroidPixelBuffer.cpp \ 8 | src/AndroidSocket.cpp \ 9 | src/InputDevice.cpp \ 10 | src/VirtualDisplay.cpp \ 11 | src/main.cpp 12 | 13 | #LOCAL_SRC_FILES += \ 14 | # aidl/org/chemlab/IVNCService.aidl 15 | 16 | LOCAL_C_INCLUDES += \ 17 | $(LOCAL_PATH)/src \ 18 | external/tigervnc/common \ 19 | 20 | LOCAL_SHARED_LIBRARIES := \ 21 | libbinder \ 22 | libcrypto \ 23 | libcutils \ 24 | libgui \ 25 | libjpeg \ 26 | libssl \ 27 | libui \ 28 | libutils \ 29 | libz 30 | 31 | LOCAL_STATIC_LIBRARIES += \ 32 | libtigervnc 33 | 34 | LOCAL_CFLAGS := -DVNCFLINGER_VERSION="1.0" 35 | LOCAL_CFLAGS += -Ofast -Werror -std=c++11 -fexceptions 36 | 37 | #LOCAL_CFLAGS += -DLOG_NDEBUG=0 38 | #LOCAL_CXX := /usr/bin/include-what-you-use 39 | 40 | LOCAL_INIT_RC := etc/vncflinger.rc 41 | 42 | LOCAL_MODULE := vncflinger 43 | 44 | LOCAL_MODULE_TAGS := optional 45 | 46 | # Mason product builds only 47 | ifneq ($(VNCFLINGER_DESKTOP_NAME),) 48 | LOCAL_CFLAGS += -DDESKTOP_NAME=\"$(VNCFLINGER_DESKTOP_NAME)\" 49 | endif 50 | 51 | include $(BUILD_EXECUTABLE) 52 | -------------------------------------------------------------------------------- /src/VNCService.h: -------------------------------------------------------------------------------- 1 | #include "org/chemlab/BnVNCService.h" 2 | 3 | #include "VNCFlinger.h" 4 | 5 | namespace android { 6 | 7 | class VNCService : public org::chemlab::BnVNCService { 8 | 9 | public: 10 | VNCService(sp flinger) : mVNC(flinger) {} 11 | 12 | binder::Status start(bool* ret) { 13 | *ret = mVNC->start(); 14 | return binder::Status::ok(); 15 | } 16 | 17 | binder::Status stop(bool* ret) { 18 | *ret = mVNC->stop() == NO_ERROR; 19 | return binder::Status::ok(); 20 | } 21 | 22 | binder::Status setPort(int32_t port, bool* ret) { 23 | *ret = mVNC->setPort(port) == NO_ERROR; 24 | return binder::Status::ok(); 25 | } 26 | 27 | binder::Status setV4Address(const String16& addr, bool* ret) { 28 | *ret = mVNC->setV4Address(String8(addr)) == NO_ERROR; 29 | return binder::Status::ok(); 30 | } 31 | 32 | binder::Status setV6Address(const String16& addr, bool* ret) { 33 | *ret = mVNC->setV6Address(String8(addr)) == NO_ERROR; 34 | return binder::Status::ok(); 35 | } 36 | 37 | binder::Status setPassword(const String16& addr, bool* ret) { 38 | *ret = mVNC->setPassword(String8(addr)) == NO_ERROR; 39 | return binder::Status::ok(); 40 | } 41 | 42 | binder::Status clearPassword(bool* ret) { 43 | *ret = mVNC->clearPassword() == NO_ERROR; 44 | return binder::Status::ok(); 45 | } 46 | 47 | private: 48 | sp mVNC; 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /src/VirtualDisplay.h: -------------------------------------------------------------------------------- 1 | // 2 | // vncflinger - Copyright (C) 2021 Stefanie Kondik 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | 18 | #ifndef VIRTUAL_DISPLAY_H_ 19 | #define VIRTUAL_DISPLAY_H_ 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | using namespace android; 30 | 31 | namespace vncflinger { 32 | 33 | class VirtualDisplay : public RefBase { 34 | public: 35 | VirtualDisplay(DisplayInfo* info, uint32_t width, uint32_t height, 36 | sp listener); 37 | 38 | virtual ~VirtualDisplay(); 39 | 40 | virtual Rect getDisplayRect(); 41 | 42 | virtual Rect getSourceRect() { 43 | return mSourceRect; 44 | } 45 | 46 | CpuConsumer* getConsumer() { 47 | return mCpuConsumer.get(); 48 | } 49 | 50 | private: 51 | float aspectRatio() { 52 | return (float)mSourceRect.getHeight() / (float)mSourceRect.getWidth(); 53 | } 54 | 55 | // Producer side of queue, passed into the virtual display. 56 | sp mProducer; 57 | 58 | // This receives frames from the virtual display and makes them available 59 | sp mCpuConsumer; 60 | 61 | // Virtual display 62 | sp mDpy; 63 | 64 | sp mListener; 65 | 66 | uint32_t mWidth, mHeight; 67 | Rect mSourceRect; 68 | }; 69 | }; 70 | #endif 71 | -------------------------------------------------------------------------------- /src/InputDevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // vncflinger - Copyright (C) 2021 Stefanie Kondik 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | 18 | #ifndef INPUT_DEVICE_H 19 | #define INPUT_DEVICE_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | 28 | #define UINPUT_DEVICE "/dev/uinput" 29 | 30 | namespace android { 31 | 32 | class InputDevice : public RefBase { 33 | public: 34 | virtual status_t start(uint32_t width, uint32_t height); 35 | virtual status_t start_async(uint32_t width, uint32_t height); 36 | virtual status_t stop(); 37 | virtual status_t reconfigure(uint32_t width, uint32_t height); 38 | 39 | virtual void keyEvent(bool down, uint32_t key); 40 | virtual void pointerEvent(int buttonMask, int x, int y); 41 | 42 | InputDevice() : mFD(-1) { 43 | } 44 | virtual ~InputDevice() { 45 | stop(); 46 | } 47 | 48 | private: 49 | 50 | status_t inject(uint16_t type, uint16_t code, int32_t value); 51 | status_t injectSyn(uint16_t type, uint16_t code, int32_t value); 52 | status_t movePointer(int32_t x, int32_t y); 53 | status_t setPointer(int32_t x, int32_t y); 54 | status_t press(uint16_t code); 55 | status_t release(uint16_t code); 56 | status_t click(uint16_t code); 57 | 58 | int keysym2scancode(uint32_t c, int* sh, int* alt); 59 | 60 | Mutex mLock; 61 | 62 | int mFD; 63 | bool mOpened; 64 | 65 | struct uinput_user_dev mUserDev; 66 | 67 | bool mLeftClicked; 68 | bool mRightClicked; 69 | bool mMiddleClicked; 70 | }; 71 | }; 72 | #endif 73 | -------------------------------------------------------------------------------- /src/AndroidDesktop.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROID_DESKTOP_H_ 2 | #define ANDROID_DESKTOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "AndroidPixelBuffer.h" 20 | #include "InputDevice.h" 21 | #include "VirtualDisplay.h" 22 | 23 | using namespace android; 24 | 25 | namespace vncflinger { 26 | 27 | class AndroidDesktop : public rfb::SDesktop, 28 | public CpuConsumer::FrameAvailableListener, 29 | public AndroidPixelBuffer::BufferDimensionsListener { 30 | public: 31 | AndroidDesktop(); 32 | 33 | virtual ~AndroidDesktop(); 34 | 35 | virtual void start(rfb::VNCServer* vs); 36 | virtual void stop(); 37 | virtual void terminate(); 38 | 39 | virtual unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout); 40 | 41 | virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); 42 | virtual void pointerEvent(const rfb::Point& pos, int buttonMask); 43 | 44 | virtual void processFrames(); 45 | 46 | virtual int getEventFd() { 47 | return mEventFd; 48 | } 49 | 50 | virtual void onBufferDimensionsChanged(uint32_t width, uint32_t height); 51 | 52 | virtual void onFrameAvailable(const BufferItem& item); 53 | 54 | virtual void queryConnection(network::Socket* sock, const char* userName); 55 | 56 | private: 57 | virtual void notify(); 58 | 59 | virtual status_t updateDisplayInfo(); 60 | 61 | virtual rfb::ScreenSet computeScreenLayout(); 62 | 63 | Rect mDisplayRect; 64 | 65 | Mutex mLock; 66 | 67 | uint64_t mFrameNumber; 68 | 69 | int mEventFd; 70 | 71 | // Server instance 72 | rfb::VNCServer* mServer; 73 | 74 | // Pixel buffer 75 | sp mPixels; 76 | 77 | // Virtual display controller 78 | sp mVirtualDisplay; 79 | 80 | // Primary display 81 | sp mMainDpy; 82 | DisplayInfo mDisplayInfo; 83 | 84 | // Virtual input device 85 | sp mInputDevice; 86 | }; 87 | }; 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /src/AndroidPixelBuffer.h: -------------------------------------------------------------------------------- 1 | // 2 | // vncflinger - Copyright (C) 2021 Stefanie Kondik 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | 18 | #ifndef ANDROID_PIXEL_BUFFER_H 19 | #define ANDROID_PIXEL_BUFFER_H 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | using namespace android; 31 | 32 | namespace vncflinger { 33 | 34 | class AndroidPixelBuffer : public RefBase, public rfb::ManagedPixelBuffer { 35 | public: 36 | AndroidPixelBuffer(); 37 | 38 | virtual void setDisplayInfo(DisplayInfo* info); 39 | 40 | virtual void setWindowSize(uint32_t width, uint32_t height); 41 | 42 | virtual ~AndroidPixelBuffer(); 43 | 44 | class BufferDimensionsListener { 45 | public: 46 | virtual void onBufferDimensionsChanged(uint32_t width, uint32_t height) = 0; 47 | virtual ~BufferDimensionsListener() { 48 | } 49 | }; 50 | 51 | void setDimensionsChangedListener(BufferDimensionsListener* listener) { 52 | mListener = listener; 53 | } 54 | 55 | bool isRotated() { 56 | return mRotated; 57 | } 58 | 59 | Rect getSourceRect(); 60 | 61 | private: 62 | static bool isDisplayRotated(uint8_t orientation); 63 | 64 | virtual void setBufferRotation(bool rotated); 65 | 66 | virtual void updateBufferSize(bool fromDisplay = false); 67 | 68 | Mutex mLock; 69 | 70 | // width/height is swapped due to display orientation 71 | bool mRotated; 72 | 73 | // preferred size of the client's window 74 | uint32_t mClientWidth, mClientHeight; 75 | 76 | // size of the display 77 | uint32_t mSourceWidth, mSourceHeight; 78 | 79 | // current ratio between server and client 80 | float mScaleX, mScaleY; 81 | 82 | // callback when buffer size changes 83 | BufferDimensionsListener* mListener; 84 | 85 | // Android virtual display is always 32-bit 86 | static const rfb::PixelFormat sRGBX; 87 | }; 88 | }; 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/VirtualDisplay.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // vncflinger - Copyright (C) 2021 Stefanie Kondik 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | 18 | #define LOG_TAG "VirtualDisplay" 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "VirtualDisplay.h" 27 | 28 | using namespace vncflinger; 29 | 30 | VirtualDisplay::VirtualDisplay(DisplayInfo* info, uint32_t width, uint32_t height, 31 | sp listener) { 32 | mWidth = width; 33 | mHeight = height; 34 | 35 | if (info->orientation == DISPLAY_ORIENTATION_0 || info->orientation == DISPLAY_ORIENTATION_180) { 36 | mSourceRect = Rect(info->w, info->h); 37 | } else { 38 | mSourceRect = Rect(info->h, info->w); 39 | } 40 | 41 | Rect displayRect = getDisplayRect(); 42 | 43 | sp consumer; 44 | BufferQueue::createBufferQueue(&mProducer, &consumer); 45 | mCpuConsumer = new CpuConsumer(consumer, 1); 46 | mCpuConsumer->setName(String8("vds-to-cpu")); 47 | mCpuConsumer->setDefaultBufferSize(width, height); 48 | mProducer->setMaxDequeuedBufferCount(4); 49 | consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBX_8888); 50 | 51 | mCpuConsumer->setFrameAvailableListener(listener); 52 | 53 | mDpy = SurfaceComposerClient::createDisplay(String8("VNC-VirtualDisplay"), false /*secure*/); 54 | 55 | SurfaceComposerClient::openGlobalTransaction(); 56 | SurfaceComposerClient::setDisplaySurface(mDpy, mProducer); 57 | 58 | SurfaceComposerClient::setDisplayProjection(mDpy, 0, mSourceRect, displayRect); 59 | SurfaceComposerClient::setDisplayLayerStack(mDpy, 0); // default stack 60 | SurfaceComposerClient::closeGlobalTransaction(); 61 | 62 | ALOGV("Virtual display (%ux%u [viewport=%ux%u] created", width, height, displayRect.getWidth(), 63 | displayRect.getHeight()); 64 | } 65 | 66 | VirtualDisplay::~VirtualDisplay() { 67 | mCpuConsumer.clear(); 68 | mProducer.clear(); 69 | SurfaceComposerClient::destroyDisplay(mDpy); 70 | 71 | ALOGV("Virtual display destroyed"); 72 | } 73 | 74 | Rect VirtualDisplay::getDisplayRect() { 75 | uint32_t outWidth, outHeight; 76 | if (mWidth > (uint32_t)((float)mWidth * aspectRatio())) { 77 | // limited by narrow width; reduce height 78 | outWidth = mWidth; 79 | outHeight = (uint32_t)((float)mWidth * aspectRatio()); 80 | } else { 81 | // limited by short height; restrict width 82 | outHeight = mHeight; 83 | outWidth = (uint32_t)((float)mHeight / aspectRatio()); 84 | } 85 | 86 | // position the desktop in the viewport while preserving 87 | // the source aspect ratio. we do this in case the client 88 | // has resized the window and to deal with orientation 89 | // changes set up by updateDisplayProjection 90 | uint32_t offX, offY; 91 | offX = (mWidth - outWidth) / 2; 92 | offY = (mHeight - outHeight) / 2; 93 | return Rect(offX, offY, offX + outWidth, offY + outHeight); 94 | } 95 | -------------------------------------------------------------------------------- /src/AndroidPixelBuffer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // vncflinger - Copyright (C) 2021 Stefanie Kondik 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | 18 | #define LOG_TAG "AndroidPixelBuffer" 19 | #include 20 | 21 | #include 22 | 23 | #include "AndroidPixelBuffer.h" 24 | 25 | using namespace vncflinger; 26 | using namespace android; 27 | 28 | const rfb::PixelFormat AndroidPixelBuffer::sRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16); 29 | 30 | AndroidPixelBuffer::AndroidPixelBuffer() 31 | : ManagedPixelBuffer(), mRotated(false), mScaleX(1.0f), mScaleY(1.0f) { 32 | setPF(sRGBX); 33 | setSize(0, 0); 34 | } 35 | 36 | AndroidPixelBuffer::~AndroidPixelBuffer() { 37 | mListener = nullptr; 38 | } 39 | 40 | bool AndroidPixelBuffer::isDisplayRotated(uint8_t orientation) { 41 | return orientation != DISPLAY_ORIENTATION_0 && orientation != DISPLAY_ORIENTATION_180; 42 | } 43 | 44 | void AndroidPixelBuffer::setBufferRotation(bool rotated) { 45 | if (rotated != mRotated) { 46 | ALOGV("Orientation changed, swap width/height"); 47 | mRotated = rotated; 48 | setSize(height_, width_); 49 | std::swap(mScaleX, mScaleY); 50 | stride = width_; 51 | 52 | if (mListener != nullptr) { 53 | mListener->onBufferDimensionsChanged(width_, height_); 54 | } 55 | } 56 | } 57 | 58 | void AndroidPixelBuffer::updateBufferSize(bool fromDisplay) { 59 | uint32_t w = 0, h = 0; 60 | 61 | // if this was caused by the source size changing (doesn't really 62 | // happen on most Android hardware), then we need to consider 63 | // a previous window size set by the client 64 | if (fromDisplay) { 65 | w = (uint32_t)((float)mSourceWidth * mScaleX); 66 | h = (uint32_t)((float)mSourceHeight * mScaleY); 67 | mClientWidth = w; 68 | mClientHeight = h; 69 | } else { 70 | w = mClientWidth; 71 | h = mClientHeight; 72 | } 73 | 74 | mScaleX = (float)mClientWidth / (float)mSourceWidth; 75 | mScaleY = (float)mClientHeight / (float)mSourceHeight; 76 | 77 | if (w == (uint32_t)width_ && h == (uint32_t)height_) { 78 | return; 79 | } 80 | 81 | ALOGV("Buffer dimensions changed: old=(%dx%d) new=(%dx%d) scaleX=%f scaleY=%f", width_, height_, 82 | w, h, mScaleX, mScaleY); 83 | 84 | setSize(w, h); 85 | 86 | if (mListener != nullptr) { 87 | mListener->onBufferDimensionsChanged(width_, height_); 88 | } 89 | } 90 | 91 | void AndroidPixelBuffer::setWindowSize(uint32_t width, uint32_t height) { 92 | if (mClientWidth != width || mClientHeight != height) { 93 | ALOGV("Client window size changed: old=(%dx%d) new=(%dx%d)", mClientWidth, mClientHeight, 94 | width, height); 95 | mClientWidth = width; 96 | mClientHeight = height; 97 | updateBufferSize(); 98 | } 99 | } 100 | 101 | void AndroidPixelBuffer::setDisplayInfo(DisplayInfo* info) { 102 | bool rotated = isDisplayRotated(info->orientation); 103 | setBufferRotation(rotated); 104 | 105 | uint32_t w = rotated ? info->h : info->w; 106 | uint32_t h = rotated ? info->w : info->h; 107 | 108 | if (w != mSourceWidth || h != mSourceHeight) { 109 | ALOGV("Display dimensions changed: old=(%dx%d) new=(%dx%d)", mSourceWidth, mSourceHeight, w, 110 | h); 111 | mSourceWidth = w; 112 | mSourceHeight = h; 113 | updateBufferSize(true); 114 | } 115 | } 116 | 117 | Rect AndroidPixelBuffer::getSourceRect() { 118 | return Rect(mSourceWidth, mSourceHeight); 119 | } 120 | -------------------------------------------------------------------------------- /src/AndroidDesktop.cpp: -------------------------------------------------------------------------------- 1 | #define LOG_TAG "AndroidDesktop" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "AndroidDesktop.h" 18 | #include "AndroidPixelBuffer.h" 19 | #include "InputDevice.h" 20 | #include "VirtualDisplay.h" 21 | 22 | using namespace vncflinger; 23 | using namespace android; 24 | 25 | AndroidDesktop::AndroidDesktop() { 26 | mInputDevice = new InputDevice(); 27 | mDisplayRect = Rect(0, 0); 28 | 29 | mEventFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); 30 | if (mEventFd < 0) { 31 | ALOGE("Failed to create event notifier"); 32 | return; 33 | } 34 | } 35 | 36 | AndroidDesktop::~AndroidDesktop() { 37 | mInputDevice->stop(); 38 | close(mEventFd); 39 | } 40 | 41 | void AndroidDesktop::start(rfb::VNCServer* vs) { 42 | mMainDpy = SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain); 43 | 44 | mServer = vs; 45 | 46 | mPixels = new AndroidPixelBuffer(); 47 | mPixels->setDimensionsChangedListener(this); 48 | 49 | if (updateDisplayInfo() != NO_ERROR) { 50 | ALOGE("Failed to query display!"); 51 | return; 52 | } 53 | 54 | ALOGV("Desktop is running"); 55 | } 56 | 57 | void AndroidDesktop::stop() { 58 | Mutex::Autolock _L(mLock); 59 | 60 | ALOGV("Shutting down"); 61 | 62 | mServer->setPixelBuffer(0); 63 | 64 | mVirtualDisplay.clear(); 65 | mPixels.clear(); 66 | } 67 | 68 | void AndroidDesktop::processFrames() { 69 | Mutex::Autolock _l(mLock); 70 | 71 | updateDisplayInfo(); 72 | 73 | // get a frame from the virtual display 74 | CpuConsumer::LockedBuffer imgBuffer; 75 | status_t res = mVirtualDisplay->getConsumer()->lockNextBuffer(&imgBuffer); 76 | if (res != OK) { 77 | ALOGE("Failed to lock next buffer: %s (%d)", strerror(-res), res); 78 | return; 79 | } 80 | 81 | mFrameNumber = imgBuffer.frameNumber; 82 | ALOGV("processFrame: [%" PRIu64 "] format: %x (%dx%d, stride=%d)", mFrameNumber, imgBuffer.format, 83 | imgBuffer.width, imgBuffer.height, imgBuffer.stride); 84 | 85 | // we don't know if there was a stride change until we get 86 | // a buffer from the queue. if it changed, we need to resize 87 | 88 | rfb::Rect bufRect(0, 0, imgBuffer.width, imgBuffer.height); 89 | 90 | // performance is extremely bad if the gpu memory is used 91 | // directly without copying because it is likely uncached 92 | mPixels->imageRect(bufRect, imgBuffer.data, imgBuffer.stride); 93 | 94 | mVirtualDisplay->getConsumer()->unlockBuffer(imgBuffer); 95 | 96 | // update clients 97 | mServer->add_changed(bufRect); 98 | } 99 | 100 | // notifies the server loop that we have changes 101 | void AndroidDesktop::notify() { 102 | static uint64_t notify = 1; 103 | write(mEventFd, ¬ify, sizeof(notify)); 104 | } 105 | 106 | // called when a client resizes the window 107 | unsigned int AndroidDesktop::setScreenLayout(int reqWidth, int reqHeight, 108 | const rfb::ScreenSet& layout) { 109 | Mutex::Autolock _l(mLock); 110 | 111 | char* dbg = new char[1024]; 112 | layout.print(dbg, 1024); 113 | 114 | ALOGD("setScreenLayout: cur: %s new: %dx%d", dbg, reqWidth, reqHeight); 115 | delete[] dbg; 116 | 117 | if (reqWidth == mDisplayRect.getWidth() && reqHeight == mDisplayRect.getHeight()) { 118 | return rfb::resultInvalid; 119 | } 120 | 121 | if (reqWidth > 0 && reqHeight > 0) { 122 | mPixels->setWindowSize(reqWidth, reqHeight); 123 | 124 | rfb::ScreenSet screens; 125 | screens.add_screen(rfb::Screen(0, 0, 0, mPixels->width(), mPixels->height(), 0)); 126 | mServer->setScreenLayout(screens); 127 | return rfb::resultSuccess; 128 | } 129 | 130 | return rfb::resultInvalid; 131 | } 132 | 133 | // cpuconsumer frame listener, called from binder thread 134 | void AndroidDesktop::onFrameAvailable(const BufferItem& item) { 135 | ALOGV("onFrameAvailable: [%" PRIu64 "] mTimestamp=%" PRId64, item.mFrameNumber, item.mTimestamp); 136 | 137 | notify(); 138 | } 139 | 140 | void AndroidDesktop::keyEvent(rdr::U32 keysym, __unused_attr rdr::U32 keycode, bool down) { 141 | mInputDevice->keyEvent(down, keysym); 142 | } 143 | 144 | void AndroidDesktop::pointerEvent(const rfb::Point& pos, int buttonMask) { 145 | if (pos.x < mDisplayRect.left || pos.x > mDisplayRect.right || pos.y < mDisplayRect.top || 146 | pos.y > mDisplayRect.bottom) { 147 | // outside viewport 148 | return; 149 | } 150 | uint32_t x = pos.x * ((float)(mDisplayRect.getWidth()) / (float)mPixels->width()); 151 | uint32_t y = pos.y * ((float)(mDisplayRect.getHeight()) / (float)mPixels->height()); 152 | 153 | ALOGV("pointer xlate x1=%d y1=%d x2=%d y2=%d", pos.x, pos.y, x, y); 154 | 155 | mServer->setCursorPos(rfb::Point(x, y)); 156 | mInputDevice->pointerEvent(buttonMask, x, y); 157 | } 158 | 159 | // refresh the display dimensions 160 | status_t AndroidDesktop::updateDisplayInfo() { 161 | status_t err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &mDisplayInfo); 162 | if (err != NO_ERROR) { 163 | ALOGE("Failed to get display characteristics\n"); 164 | return err; 165 | } 166 | 167 | mPixels->setDisplayInfo(&mDisplayInfo); 168 | 169 | return NO_ERROR; 170 | } 171 | 172 | rfb::ScreenSet AndroidDesktop::computeScreenLayout() { 173 | rfb::ScreenSet screens; 174 | screens.add_screen(rfb::Screen(0, 0, 0, mPixels->width(), mPixels->height(), 0)); 175 | return screens; 176 | mServer->setScreenLayout(screens); 177 | } 178 | 179 | void AndroidDesktop::onBufferDimensionsChanged(uint32_t width, uint32_t height) { 180 | ALOGV("Dimensions changed: old=(%ux%u) new=(%ux%u)", mDisplayRect.getWidth(), 181 | mDisplayRect.getHeight(), width, height); 182 | 183 | mVirtualDisplay.clear(); 184 | mVirtualDisplay = new VirtualDisplay(&mDisplayInfo, mPixels->width(), mPixels->height(), this); 185 | 186 | mDisplayRect = mVirtualDisplay->getDisplayRect(); 187 | 188 | mInputDevice->reconfigure(mDisplayRect.getWidth(), mDisplayRect.getHeight()); 189 | 190 | mServer->setPixelBuffer(mPixels.get(), computeScreenLayout()); 191 | mServer->setScreenLayout(computeScreenLayout()); 192 | } 193 | 194 | void AndroidDesktop::queryConnection(network::Socket* sock, __unused_attr const char* userName) { 195 | mServer->approveConnection(sock, true, NULL); 196 | } 197 | 198 | void AndroidDesktop::terminate() { 199 | kill(getpid(), SIGTERM); 200 | } 201 | -------------------------------------------------------------------------------- /tools/update-headers: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # update-headers - Updates the header comment in source files 4 | # Copyright (C) 2013 Lorenzo Villani 5 | # Updated for Python3 by Stefanie Kondik (part of UChroma) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | 21 | 22 | from argparse import ArgumentParser 23 | from functools import partial 24 | from os import chmod, stat, walk 25 | from os.path import isdir, isfile, join, splitext 26 | from shutil import copyfile 27 | from tempfile import NamedTemporaryFile 28 | 29 | 30 | # File extension -> comment string 31 | COMMENT_STRING = { 32 | ".c": "//", 33 | ".cc": "//", 34 | ".cpp": "//", 35 | ".el": ";;;;", 36 | ".h": "//", 37 | ".hh": "//", 38 | ".hpp": "//", 39 | ".hs": "--", 40 | ".java": "//", 41 | ".js": "//", 42 | ".li": ";;;;", 43 | ".m": "//", 44 | ".mm": "//", 45 | ".py": "#", 46 | ".swift": "//", 47 | ".yml": "#", 48 | } 49 | 50 | 51 | def main(): 52 | # Parse command line 53 | arg_parser = ArgumentParser() 54 | arg_parser.add_argument("-c", "--comment-string", default="#") 55 | arg_parser.add_argument("header", nargs=1) 56 | arg_parser.add_argument("path", nargs="+") 57 | 58 | args = arg_parser.parse_args() 59 | 60 | # Input stream for the header boilerplate 61 | header = open(args.header[0], "r") 62 | 63 | # Process files and directories 64 | for path in args.path: 65 | if isfile(path): 66 | update_file(path, header, args.comment_string) 67 | elif isdir(path): 68 | update_directory(path, header, args.comment_string) 69 | 70 | 71 | def update_file(path, header, comment_string): 72 | """ 73 | Updates the header boilerplate for a single file. 74 | 75 | :param path: A file path. 76 | """ 77 | original_stat = stat(path) 78 | input_stream = open(path, "r") 79 | tempfile = NamedTemporaryFile(mode="w") 80 | 81 | update_header(input_stream, header, tempfile, comment_string) 82 | tempfile.flush() 83 | 84 | copyfile(tempfile.name, path) 85 | chmod(path, original_stat.st_mode) 86 | 87 | 88 | def update_directory(path, header, comment_string): 89 | """ 90 | Recursively updates the header boilerplate for all recognized files below 91 | the specified directory. 92 | 93 | :param path: A directory path. 94 | """ 95 | def is_recognized(entry): 96 | return isfile(entry) and splitext(entry)[1] in COMMENT_STRING 97 | 98 | def visit(_, directory, files): 99 | entries = list(map(partial(join, directory), files)) 100 | applicable = list(filter(is_recognized, entries)) 101 | 102 | for path in applicable: 103 | ext = splitext(path)[1] 104 | 105 | update_file(path, header, COMMENT_STRING[ext]) 106 | 107 | for dirpath, dirnames, filenames in walk(path): 108 | visit(None, dirpath, dirnames + filenames) 109 | 110 | 111 | def update_header(input_stream, new_header, output_stream, comment_string="#"): 112 | """ 113 | Replaces or inserts a new header in a file. 114 | 115 | @param input_stream: The input file with the old (or missing) header. 116 | @type input_stream: file 117 | 118 | @param new_header: Input stream of the new header. 119 | @type new_header: file 120 | 121 | @param output_stream: Output stream for the new file with the header 122 | replaced. 123 | @type output_stream: file 124 | 125 | @param comment_string: The string used to start a comment which spans until 126 | the end of the line. 127 | @type comment_string: str 128 | """ 129 | class State: 130 | Start, FoundHeaderStart, Done = list(range(3)) 131 | 132 | state = State.Start 133 | 134 | for line in input_stream: 135 | if state == State.Start: 136 | # At the beginning of the file. 137 | if line.startswith("#!"): 138 | # Ignore the shebang and copy this line as-is. No state change. 139 | output_stream.write(line) 140 | elif line.startswith(comment_string): 141 | # Start -> FoundHeaderStart. 142 | state = State.FoundHeaderStart 143 | else: 144 | # Inject header then state transition: Start -> Done. 145 | inject_header(new_header, output_stream, comment_string) 146 | output_stream.write(line) 147 | 148 | state = State.Done 149 | elif state == State.FoundHeaderStart: 150 | # We have found the beginning of the header comment, now we have to 151 | # look for the first non comment line, then inject our header, 152 | # then transition from FoundHeaderStart -> Done. 153 | if not line.startswith(comment_string): 154 | inject_header(new_header, output_stream, comment_string) 155 | output_stream.write(line) 156 | state = State.Done 157 | elif state == State.Done: 158 | # Copy input to output verbatim 159 | output_stream.write(line) 160 | else: 161 | if state == State.Start or state == State.FoundHeaderStart: 162 | # Input is empty, inject the header and be done with it 163 | inject_header(new_header, output_stream, comment_string) 164 | 165 | state = State.Done 166 | 167 | output_stream.flush() 168 | 169 | 170 | def inject_header(header_stream, output_stream, comment_string): 171 | """ 172 | Writes the header onto the output stream. 173 | 174 | @param header_stream: Input stream for the header. A seek to the beginning 175 | of the file is performed with every call. 176 | @type header_stream: file 177 | 178 | @param output_stream: The stream where the header will be written. 179 | @type output_stream: file 180 | 181 | @param comment_string: The string used to start a comment which spans until 182 | the end of the line. 183 | @type comment_string: str 184 | """ 185 | header_stream.seek(0) 186 | 187 | for line in header_stream: 188 | if line.strip() == "": 189 | output_stream.write(comment_string + "\n") 190 | else: 191 | output_stream.write(comment_string + " " + line.rstrip(' ')) 192 | 193 | if __name__ == "__main__": 194 | main() 195 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #define LOG_TAG "VNCFlinger" 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "AndroidDesktop.h" 8 | #include "AndroidSocket.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | 25 | using namespace vncflinger; 26 | using namespace android; 27 | 28 | static char* gProgramName; 29 | static bool gCaughtSignal = false; 30 | static char gSerialNo[PROPERTY_VALUE_MAX]; 31 | 32 | #ifndef DESKTOP_NAME 33 | #define DESKTOP_NAME "VNCFlinger" 34 | #endif 35 | 36 | static rfb::IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol", 5900); 37 | static rfb::BoolParameter localhostOnly("localhost", "Only allow connections from localhost", false); 38 | static rfb::StringParameter rfbunixpath("rfbunixpath", "Unix socket to listen for RFB protocol", ""); 39 | static rfb::IntParameter rfbunixmode("rfbunixmode", "Unix socket access mode", 0600); 40 | 41 | static void printVersion(FILE* fp) { 42 | fprintf(fp, "VNCFlinger 1.0"); 43 | } 44 | 45 | static void usage() { 46 | printVersion(stderr); 47 | fprintf(stderr, "\nUsage: %s []\n", gProgramName); 48 | fprintf(stderr, " %s --version\n", gProgramName); 49 | fprintf(stderr, 50 | "\n" 51 | "Parameters can be turned on with - or off with -=0\n" 52 | "Parameters which take a value can be specified as " 53 | "- \n" 54 | "Other valid forms are = -= " 55 | "--=\n" 56 | "Parameter names are case-insensitive. The parameters are:\n\n"); 57 | rfb::Configuration::listParams(79, 14); 58 | exit(1); 59 | } 60 | 61 | int main(int argc, char** argv) { 62 | rfb::initAndroidLogger(); 63 | rfb::LogWriter::setLogParams("*:android:30"); 64 | 65 | gProgramName = argv[0]; 66 | property_get("ro.serialno", gSerialNo, ""); 67 | std::string desktopName = DESKTOP_NAME; 68 | desktopName += " @ "; 69 | desktopName += (const char *)gSerialNo; 70 | 71 | rfb::Configuration::enableServerParams(); 72 | 73 | for (int i = 1; i < argc; i++) { 74 | if (rfb::Configuration::setParam(argv[i])) continue; 75 | 76 | if (argv[i][0] == '-') { 77 | if (i + 1 < argc) { 78 | if (rfb::Configuration::setParam(&argv[i][1], argv[i + 1])) { 79 | i++; 80 | continue; 81 | } 82 | } 83 | if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "-version") == 0 || 84 | strcmp(argv[i], "--version") == 0) { 85 | printVersion(stdout); 86 | return 0; 87 | } 88 | usage(); 89 | } 90 | usage(); 91 | } 92 | 93 | sp self = ProcessState::self(); 94 | self->startThreadPool(); 95 | 96 | std::list listeners; 97 | 98 | try { 99 | sp desktop = new AndroidDesktop(); 100 | rfb::VNCServerST server(desktopName.c_str(), desktop.get()); 101 | 102 | if (rfbunixpath.getValueStr()[0] != '\0') { 103 | listeners.push_back(new AndroidListener("vncflinger")); 104 | ALOGI("Listening on %s (mode %04o)", (const char*)rfbunixpath, (int)rfbunixmode); 105 | } else { 106 | if (localhostOnly) { 107 | network::createLocalTcpListeners(&listeners, (int)rfbport); 108 | } else { 109 | network::createTcpListeners(&listeners, 0, (int)rfbport); 110 | ALOGI("Listening on port %d", (int)rfbport); 111 | } 112 | } 113 | 114 | int eventFd = desktop->getEventFd(); 115 | fcntl(eventFd, F_SETFL, O_NONBLOCK); 116 | 117 | while (!gCaughtSignal) { 118 | int wait_ms; 119 | struct timeval tv; 120 | fd_set rfds, wfds; 121 | std::list sockets; 122 | std::list::iterator i; 123 | 124 | FD_ZERO(&rfds); 125 | FD_ZERO(&wfds); 126 | 127 | FD_SET(eventFd, &rfds); 128 | for (std::list::iterator i = listeners.begin(); 129 | i != listeners.end(); i++) 130 | FD_SET((*i)->getFd(), &rfds); 131 | 132 | server.getSockets(&sockets); 133 | int clients_connected = 0; 134 | for (i = sockets.begin(); i != sockets.end(); i++) { 135 | if ((*i)->isShutdown()) { 136 | server.removeSocket(*i); 137 | delete (*i); 138 | } else { 139 | FD_SET((*i)->getFd(), &rfds); 140 | if ((*i)->outStream().bufferUsage() > 0) { 141 | FD_SET((*i)->getFd(), &wfds); 142 | } 143 | clients_connected++; 144 | } 145 | } 146 | 147 | wait_ms = 0; 148 | 149 | rfb::soonestTimeout(&wait_ms, rfb::Timer::checkTimeouts()); 150 | 151 | tv.tv_sec = wait_ms / 1000; 152 | tv.tv_usec = (wait_ms % 1000) * 1000; 153 | 154 | int n = select(FD_SETSIZE, &rfds, &wfds, 0, wait_ms ? &tv : NULL); 155 | 156 | if (n < 0) { 157 | if (errno == EINTR) { 158 | ALOGV("Interrupted select() system call"); 159 | continue; 160 | } else { 161 | throw rdr::SystemException("select", errno); 162 | } 163 | } 164 | 165 | // Accept new VNC connections 166 | for (std::list::iterator i = listeners.begin(); 167 | i != listeners.end(); i++) { 168 | if (FD_ISSET((*i)->getFd(), &rfds)) { 169 | network::Socket* sock = (*i)->accept(); 170 | if (sock) { 171 | sock->outStream().setBlocking(false); 172 | server.addSocket(sock); 173 | } else { 174 | ALOGW("Client connection rejected"); 175 | } 176 | } 177 | } 178 | 179 | rfb::Timer::checkTimeouts(); 180 | 181 | // Client list could have been changed. 182 | server.getSockets(&sockets); 183 | 184 | // Nothing more to do if there are no client connections. 185 | if (sockets.empty()) continue; 186 | 187 | // Process events on existing VNC connections 188 | for (i = sockets.begin(); i != sockets.end(); i++) { 189 | if (FD_ISSET((*i)->getFd(), &rfds)) server.processSocketReadEvent(*i); 190 | if (FD_ISSET((*i)->getFd(), &wfds)) server.processSocketWriteEvent(*i); 191 | } 192 | 193 | // Process events from the display 194 | uint64_t eventVal; 195 | int status = read(eventFd, &eventVal, sizeof(eventVal)); 196 | if (status > 0 && eventVal > 0) { 197 | ALOGV("status=%d eventval=%" PRIu64, status, eventVal); 198 | desktop->processFrames(); 199 | } 200 | 201 | } 202 | 203 | } catch (rdr::Exception& e) { 204 | ALOGE("%s", e.str()); 205 | return 1; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/InputDevice.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // vncflinger - Copyright (C) 2021 Stefanie Kondik 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | 18 | #define LOG_TAG "VNC-InputDevice" 19 | #include 20 | 21 | #include 22 | 23 | #include "InputDevice.h" 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | using namespace android; 34 | 35 | 36 | static const struct UInputOptions { 37 | int cmd; 38 | int bit; 39 | } kOptions[] = { 40 | {UI_SET_EVBIT, EV_KEY}, 41 | {UI_SET_EVBIT, EV_REP}, 42 | {UI_SET_EVBIT, EV_REL}, 43 | {UI_SET_RELBIT, REL_X}, 44 | {UI_SET_RELBIT, REL_Y}, 45 | {UI_SET_RELBIT, REL_WHEEL}, 46 | {UI_SET_EVBIT, EV_ABS}, 47 | {UI_SET_ABSBIT, ABS_X}, 48 | {UI_SET_ABSBIT, ABS_Y}, 49 | {UI_SET_EVBIT, EV_SYN}, 50 | {UI_SET_PROPBIT, INPUT_PROP_DIRECT}, 51 | }; 52 | 53 | status_t InputDevice::start_async(uint32_t width, uint32_t height) { 54 | // don't block the caller since this can take a few seconds 55 | std::async(&InputDevice::start, this, width, height); 56 | 57 | return NO_ERROR; 58 | } 59 | 60 | status_t InputDevice::start(uint32_t width, uint32_t height) { 61 | Mutex::Autolock _l(mLock); 62 | 63 | mLeftClicked = mMiddleClicked = mRightClicked = false; 64 | 65 | struct input_id id = { 66 | BUS_VIRTUAL, /* Bus type */ 67 | 1, /* Vendor */ 68 | 1, /* Product */ 69 | 4, /* Version */ 70 | }; 71 | 72 | if (mFD >= 0) { 73 | ALOGE("Input device already open!"); 74 | return NO_INIT; 75 | } 76 | 77 | mFD = open(UINPUT_DEVICE, O_WRONLY | O_NONBLOCK); 78 | if (mFD < 0) { 79 | ALOGE("Failed to open %s: err=%d", UINPUT_DEVICE, mFD); 80 | return NO_INIT; 81 | } 82 | 83 | unsigned int idx = 0; 84 | for (idx = 0; idx < sizeof(kOptions) / sizeof(kOptions[0]); idx++) { 85 | if (ioctl(mFD, kOptions[idx].cmd, kOptions[idx].bit) < 0) { 86 | ALOGE("uinput ioctl failed: %d %d", kOptions[idx].cmd, kOptions[idx].bit); 87 | goto err_ioctl; 88 | } 89 | } 90 | 91 | for (idx = 0; idx < KEY_MAX; idx++) { 92 | if (ioctl(mFD, UI_SET_KEYBIT, idx) < 0) { 93 | ALOGE("UI_SET_KEYBIT failed"); 94 | goto err_ioctl; 95 | } 96 | } 97 | 98 | memset(&mUserDev, 0, sizeof(mUserDev)); 99 | strncpy(mUserDev.name, "VNC-RemoteInput", UINPUT_MAX_NAME_SIZE); 100 | 101 | mUserDev.id = id; 102 | 103 | mUserDev.absmin[ABS_X] = 0; 104 | mUserDev.absmax[ABS_X] = width; 105 | mUserDev.absmin[ABS_Y] = 0; 106 | mUserDev.absmax[ABS_Y] = height; 107 | 108 | if (write(mFD, &mUserDev, sizeof(mUserDev)) != sizeof(mUserDev)) { 109 | ALOGE("Failed to configure uinput device"); 110 | goto err_ioctl; 111 | } 112 | 113 | if (ioctl(mFD, UI_DEV_CREATE) == -1) { 114 | ALOGE("UI_DEV_CREATE failed"); 115 | goto err_ioctl; 116 | } 117 | 118 | mOpened = true; 119 | 120 | ALOGD("Virtual input device created successfully (%dx%d)", width, height); 121 | return NO_ERROR; 122 | 123 | err_ioctl: 124 | int prev_errno = errno; 125 | ::close(mFD); 126 | errno = prev_errno; 127 | mFD = -1; 128 | return NO_INIT; 129 | } 130 | 131 | status_t InputDevice::reconfigure(uint32_t width, uint32_t height) { 132 | stop(); 133 | return start_async(width, height); 134 | } 135 | 136 | status_t InputDevice::stop() { 137 | Mutex::Autolock _l(mLock); 138 | 139 | mOpened = false; 140 | 141 | if (mFD < 0) { 142 | return OK; 143 | } 144 | 145 | ioctl(mFD, UI_DEV_DESTROY); 146 | close(mFD); 147 | mFD = -1; 148 | 149 | return OK; 150 | } 151 | 152 | status_t InputDevice::inject(uint16_t type, uint16_t code, int32_t value) { 153 | struct input_event event; 154 | memset(&event, 0, sizeof(event)); 155 | gettimeofday(&event.time, 0); /* This should not be able to fail ever.. */ 156 | event.type = type; 157 | event.code = code; 158 | event.value = value; 159 | if (write(mFD, &event, sizeof(event)) != sizeof(event)) return BAD_VALUE; 160 | return OK; 161 | } 162 | 163 | status_t InputDevice::injectSyn(uint16_t type, uint16_t code, int32_t value) { 164 | if (inject(type, code, value) != OK) { 165 | return BAD_VALUE; 166 | } 167 | return inject(EV_SYN, SYN_REPORT, 0); 168 | } 169 | 170 | status_t InputDevice::movePointer(int32_t x, int32_t y) { 171 | if (inject(EV_REL, REL_X, x) != OK) { 172 | return BAD_VALUE; 173 | } 174 | return injectSyn(EV_REL, REL_Y, y); 175 | } 176 | 177 | status_t InputDevice::setPointer(int32_t x, int32_t y) { 178 | if (inject(EV_ABS, ABS_X, x) != OK) { 179 | return BAD_VALUE; 180 | } 181 | return injectSyn(EV_ABS, ABS_Y, y); 182 | } 183 | 184 | status_t InputDevice::press(uint16_t code) { 185 | return inject(EV_KEY, code, 1); 186 | } 187 | 188 | status_t InputDevice::release(uint16_t code) { 189 | return inject(EV_KEY, code, 0); 190 | } 191 | 192 | status_t InputDevice::click(uint16_t code) { 193 | if (press(code) != OK) { 194 | return BAD_VALUE; 195 | } 196 | return release(code); 197 | } 198 | 199 | void InputDevice::keyEvent(bool down, uint32_t key) { 200 | int code; 201 | int sh = 0; 202 | int alt = 0; 203 | 204 | Mutex::Autolock _l(mLock); 205 | if (!mOpened) return; 206 | 207 | if ((code = keysym2scancode(key, &sh, &alt))) { 208 | int ret = 0; 209 | 210 | if (key && down) { 211 | if (sh) press(42); // left shift 212 | if (alt) press(56); // left alt 213 | 214 | inject(EV_SYN, SYN_REPORT, 0); 215 | 216 | ret = press(code); 217 | if (ret != 0) { 218 | ALOGE("Error: %d (%s)\n", errno, strerror(errno)); 219 | } 220 | 221 | inject(EV_SYN, SYN_REPORT, 0); 222 | 223 | ret = release(code); 224 | if (ret != 0) { 225 | ALOGE("Error: %d (%s)\n", errno, strerror(errno)); 226 | } 227 | 228 | inject(EV_SYN, SYN_REPORT, 0); 229 | 230 | if (alt) release(56); // left alt 231 | if (sh) release(42); // left shift 232 | 233 | inject(EV_SYN, SYN_REPORT, 0); 234 | } 235 | } 236 | } 237 | 238 | void InputDevice::pointerEvent(int buttonMask, int x, int y) { 239 | Mutex::Autolock _l(mLock); 240 | if (!mOpened) return; 241 | 242 | ALOGV("pointerEvent: buttonMask=%x x=%d y=%d", buttonMask, x, y); 243 | 244 | if ((buttonMask & 1) && mLeftClicked) { // left btn clicked and moving 245 | inject(EV_ABS, ABS_X, x); 246 | inject(EV_ABS, ABS_Y, y); 247 | inject(EV_SYN, SYN_REPORT, 0); 248 | 249 | } else if (buttonMask & 1) { // left btn clicked 250 | mLeftClicked = true; 251 | 252 | inject(EV_ABS, ABS_X, x); 253 | inject(EV_ABS, ABS_Y, y); 254 | inject(EV_KEY, BTN_TOUCH, 1); 255 | inject(EV_SYN, SYN_REPORT, 0); 256 | } else if (mLeftClicked) // left btn released 257 | { 258 | mLeftClicked = false; 259 | inject(EV_ABS, ABS_X, x); 260 | inject(EV_ABS, ABS_Y, y); 261 | inject(EV_KEY, BTN_TOUCH, 0); 262 | inject(EV_SYN, SYN_REPORT, 0); 263 | } 264 | 265 | if (buttonMask & 4) // right btn clicked 266 | { 267 | mRightClicked = true; 268 | press(158); // back key 269 | inject(EV_SYN, SYN_REPORT, 0); 270 | } else if (mRightClicked) // right button released 271 | { 272 | mRightClicked = false; 273 | release(158); 274 | inject(EV_SYN, SYN_REPORT, 0); 275 | } 276 | 277 | if (buttonMask & 2) // mid btn clicked 278 | { 279 | mMiddleClicked = true; 280 | press(KEY_END); 281 | inject(EV_SYN, SYN_REPORT, 0); 282 | } else if (mMiddleClicked) // mid btn released 283 | { 284 | mMiddleClicked = false; 285 | release(KEY_END); 286 | inject(EV_SYN, SYN_REPORT, 0); 287 | } 288 | 289 | if (buttonMask & 8) { 290 | inject(EV_REL, REL_WHEEL, 1); 291 | inject(EV_SYN, SYN_REPORT, 0); 292 | } 293 | 294 | if (buttonMask & 0x10) { 295 | inject(EV_REL, REL_WHEEL, -1); 296 | inject(EV_SYN, SYN_REPORT, 0); 297 | } 298 | } 299 | 300 | // q,w,e,r,t,y,u,i,o,p,a,s,d,f,g,h,j,k,l,z,x,c,v,b,n,m 301 | static const int qwerty[] = {30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 302 | 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44}; 303 | // ,!,",#,$,%,&,',(,),*,+,,,-,.,/ 304 | static const int spec1[] = {57, 2, 40, 4, 5, 6, 8, 40, 10, 11, 9, 13, 51, 12, 52, 52}; 305 | static const int spec1sh[] = {0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1}; 306 | // :,;,<,=,>,?,@ 307 | static const int spec2[] = {39, 39, 227, 13, 228, 53, 3}; 308 | static const int spec2sh[] = {1, 0, 1, 1, 1, 1, 1}; 309 | // [,\,],^,_,` 310 | static const int spec3[] = {26, 43, 27, 7, 12, 399}; 311 | static const int spec3sh[] = {0, 0, 0, 1, 1, 0}; 312 | // {,|,},~ 313 | static const int spec4[] = {26, 43, 27, 215, 14}; 314 | static const int spec4sh[] = {1, 1, 1, 1, 0}; 315 | 316 | int InputDevice::keysym2scancode(uint32_t c, int* sh, int* alt) { 317 | int real = 1; 318 | if ('a' <= c && c <= 'z') return qwerty[c - 'a']; 319 | if ('A' <= c && c <= 'Z') { 320 | (*sh) = 1; 321 | return qwerty[c - 'A']; 322 | } 323 | if ('1' <= c && c <= '9') return c - '1' + 2; 324 | if (c == '0') return 11; 325 | if (32 <= c && c <= 47) { 326 | (*sh) = spec1sh[c - 32]; 327 | return spec1[c - 32]; 328 | } 329 | if (58 <= c && c <= 64) { 330 | (*sh) = spec2sh[c - 58]; 331 | return spec2[c - 58]; 332 | } 333 | if (91 <= c && c <= 96) { 334 | (*sh) = spec3sh[c - 91]; 335 | return spec3[c - 91]; 336 | } 337 | if (123 <= c && c <= 127) { 338 | (*sh) = spec4sh[c - 123]; 339 | return spec4[c - 123]; 340 | } 341 | switch (c) { 342 | case 0xff08: 343 | return 14; // backspace 344 | case 0xff09: 345 | return 15; // tab 346 | case 1: 347 | (*alt) = 1; 348 | return 34; // ctrl+a 349 | case 3: 350 | (*alt) = 1; 351 | return 46; // ctrl+c 352 | case 4: 353 | (*alt) = 1; 354 | return 32; // ctrl+d 355 | case 18: 356 | (*alt) = 1; 357 | return 31; // ctrl+r 358 | case 0xff0D: 359 | return 28; // enter 360 | case 0xff1B: 361 | return 158; // esc -> back 362 | case 0xFF51: 363 | return 105; // left -> DPAD_LEFT 364 | case 0xFF53: 365 | return 106; // right -> DPAD_RIGHT 366 | case 0xFF54: 367 | return 108; // down -> DPAD_DOWN 368 | case 0xFF52: 369 | return 103; // up -> DPAD_UP 370 | // case 360: 371 | // return 232;// end -> DPAD_CENTER (ball click) 372 | case 0xff50: 373 | return KEY_HOME; // home 374 | case 0xffff: 375 | return 158; // del -> back 376 | case 0xff55: 377 | return 229; // PgUp -> menu 378 | case 0xffcf: 379 | return 127; // F2 -> search 380 | case 0xffe3: 381 | return 127; // left ctrl -> search 382 | case 0xff56: 383 | return 61; // PgUp -> call 384 | case 0xff57: 385 | return 107; // End -> endcall 386 | case 0xffc2: 387 | return 211; // F5 -> focus 388 | case 0xffc3: 389 | return 212; // F6 -> camera 390 | case 0xffc4: 391 | return 150; // F7 -> explorer 392 | case 0xffc5: 393 | return 155; // F8 -> envelope 394 | 395 | case 50081: 396 | case 225: 397 | (*alt) = 1; 398 | if (real) return 48; // a with acute 399 | return 30; // a with acute -> a with ring above 400 | 401 | case 50049: 402 | case 193: 403 | (*sh) = 1; 404 | (*alt) = 1; 405 | if (real) return 48; // A with acute 406 | return 30; // A with acute -> a with ring above 407 | 408 | case 50089: 409 | case 233: 410 | (*alt) = 1; 411 | return 18; // e with acute 412 | 413 | case 50057: 414 | case 201: 415 | (*sh) = 1; 416 | (*alt) = 1; 417 | return 18; // E with acute 418 | 419 | case 50093: 420 | case 0xffbf: 421 | (*alt) = 1; 422 | if (real) return 36; // i with acute 423 | return 23; // i with acute -> i with grave 424 | 425 | case 50061: 426 | case 205: 427 | (*sh) = 1; 428 | (*alt) = 1; 429 | if (real) return 36; // I with acute 430 | return 23; // I with acute -> i with grave 431 | 432 | case 50099: 433 | case 243: 434 | (*alt) = 1; 435 | if (real) return 16; // o with acute 436 | return 24; // o with acute -> o with grave 437 | 438 | case 50067: 439 | case 211: 440 | (*sh) = 1; 441 | (*alt) = 1; 442 | if (real) return 16; // O with acute 443 | return 24; // O with acute -> o with grave 444 | 445 | case 50102: 446 | case 246: 447 | (*alt) = 1; 448 | return 25; // o with diaeresis 449 | 450 | case 50070: 451 | case 214: 452 | (*sh) = 1; 453 | (*alt) = 1; 454 | return 25; // O with diaeresis 455 | 456 | case 50577: 457 | case 245: 458 | (*alt) = 1; 459 | if (real) return 19; // Hungarian o 460 | return 25; // Hungarian o -> o with diaeresis 461 | 462 | case 50576: 463 | case 213: 464 | (*sh) = 1; 465 | (*alt) = 1; 466 | if (real) return 19; // Hungarian O 467 | return 25; // Hungarian O -> O with diaeresis 468 | 469 | case 50106: 470 | // case 0xffbe: 471 | // (*alt)=1; 472 | // if (real) 473 | // return 17; //u with acute 474 | // return 22; //u with acute -> u with grave 475 | case 50074: 476 | case 218: 477 | (*sh) = 1; 478 | (*alt) = 1; 479 | if (real) return 17; // U with acute 480 | return 22; // U with acute -> u with grave 481 | case 50108: 482 | case 252: 483 | (*alt) = 1; 484 | return 47; // u with diaeresis 485 | 486 | case 50076: 487 | case 220: 488 | (*sh) = 1; 489 | (*alt) = 1; 490 | return 47; // U with diaeresis 491 | 492 | case 50609: 493 | case 251: 494 | (*alt) = 1; 495 | if (real) return 45; // Hungarian u 496 | return 47; // Hungarian u -> u with diaeresis 497 | 498 | case 50608: 499 | case 219: 500 | (*sh) = 1; 501 | (*alt) = 1; 502 | if (real) return 45; // Hungarian U 503 | return 47; // Hungarian U -> U with diaeresis 504 | } 505 | return 0; 506 | } 507 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------