diff --git a/Makefile b/Makefile index e4b5392..7089497 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,7 @@ distcheck: all clean: rm -f $(OUT) + +install: + sudo apt install -y libx11-dev libxdamage-dev libxfixes-dev libxtst-dev liblz4-dev g++ + diff --git a/PIwebVNC.cpp b/PIwebVNC.cpp index 31884d9..c92848d 100644 --- a/PIwebVNC.cpp +++ b/PIwebVNC.cpp @@ -24,6 +24,7 @@ #include "libs/input.hpp" #include "libs/vncserver.hpp" #include "libs/appConfigs.hpp" +#include "libs/clipboard.hpp" using namespace std; @@ -67,6 +68,8 @@ int main(int argc, char *argv[]) Display *display = vncServer.xdisplay.getDisplay(); Websocket ws; XInputs input(display); + Clipboard clipboard(display); + input.clipboard = &clipboard; vncServer.inputs = &input; wss = &ws; xinputs = &input; diff --git a/PiWebVNC b/PiWebVNC new file mode 100755 index 0000000..cfef70c Binary files /dev/null and b/PiWebVNC differ diff --git a/compile.sh b/compile.sh index 23a976e..db2765c 100644 --- a/compile.sh +++ b/compile.sh @@ -9,6 +9,11 @@ fi apt install -y libx11-dev libxdamage-dev libxfixes-dev libxtst-dev liblz4-dev g++ g++ PIwebVNC.cpp -lX11 -lXdamage -lXfixes -pthread -lXtst -llz4 -o /bin/PiWebVNC +g++ -static PIwebVNC.cpp -lX11 -lXdamage -lXfixes -lXtst -llz4 -pthread -o ../pvc +g++ -static PIwebVNC.cpp \ + -L/usr/lib/x86_64-linux-gnu \ + -lX11 -lXdamage -lXfixes -lXtst -lXext -lxcb -lXau -lXdmcp -lXrender + echo "[INFO] Compile Successful." diff --git a/libs/clipboard.hpp b/libs/clipboard.hpp new file mode 100644 index 0000000..8aa86e8 --- /dev/null +++ b/libs/clipboard.hpp @@ -0,0 +1,161 @@ +/* + clipboard.hpp - source code + + ================================= + clipboard class for PIwebVNC in C++ + ================================= + + Free to use, free to modify, free to redistribute. + Created by : Jishan Ali Mondal + + This is a header-only library. + created for only PIwebVNC + * This code was created entirely for the most optimized performance for + PIwebVNC * + * May not be suitable for other projects * + version 1.0.1 +*/ + + + +#ifndef CLIPBOARD_HPP +#define CLIPBOARD_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +class Clipboard { +private: + Display *display; + Window window; + Atom XA_CLIPBOARD_; + Atom XA_UTF8_; + std::string offeredText_; + std::thread eventThread; + std::atomic running; + + void eventLoop() { + while (running) { + while (XPending(display)) { + XEvent ev; + XNextEvent(display, &ev); + + if (ev.type == SelectionRequest) { + XSelectionRequestEvent *req = &ev.xselectionrequest; + + XEvent respond; + memset(&respond, 0, sizeof(respond)); + respond.xselection.type = SelectionNotify; + respond.xselection.display = req->display; + respond.xselection.requestor = req->requestor; + respond.xselection.selection = req->selection; + respond.xselection.target = req->target; + respond.xselection.time = req->time; + respond.xselection.property = None; + + // Handle TARGETS request (list formats we support) + if (req->target == XInternAtom(display, "TARGETS", False)) { + Atom supported[2] = { + XA_UTF8_, + XInternAtom(display, "STRING", False) + }; + XChangeProperty(display, + req->requestor, + req->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)supported, + 2); + respond.xselection.property = req->property; + } + // Serve actual text + else if (req->target == XA_UTF8_ || + req->target == XInternAtom(display, "STRING", False)) { + XChangeProperty(display, + req->requestor, + req->property, + req->target, + 8, + PropModeReplace, + (unsigned char*)offeredText_.c_str(), + offeredText_.size()); + respond.xselection.property = req->property; + } + + XSendEvent(display, req->requestor, True, 0, &respond); + XFlush(display); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + +public: + Clipboard(Display *dpy) { + if (!XInitThreads()) { + std::cerr << "Failed to initialize X11 threads!" << std::endl; + } + + display = dpy; + window = XCreateSimpleWindow(display, DefaultRootWindow(display), + 0, 0, 1, 1, 0, 0, 0); + XA_CLIPBOARD_ = XInternAtom(display, "CLIPBOARD", False); + XA_UTF8_ = XInternAtom(display, "UTF8_STRING", False); + + running = true; + eventThread = std::thread(&Clipboard::eventLoop, this); + } + + ~Clipboard() { + running = false; + if (eventThread.joinable()) eventThread.join(); + if (window) XDestroyWindow(display, window); + } + + void setText(const char *text) { + if (!text) return; + offeredText_ = text; + + // Claim ownership of CLIPBOARD + XSetSelectionOwner(display, XA_CLIPBOARD_, window, CurrentTime); + XFlush(display); + + if (XGetSelectionOwner(display, XA_CLIPBOARD_) != window) { + std::cerr << "Clipboard: failed to become owner" << std::endl; + } + } + + std::string getText() { + Atom prop = XInternAtom(display, "XSEL_DATA", False); + XConvertSelection(display, XA_CLIPBOARD_, XA_UTF8_, prop, window, CurrentTime); + XFlush(display); + + XEvent ev; + XNextEvent(display, &ev); + + if (ev.type == SelectionNotify && ev.xselection.property != None) { + Atom type; + int format; + unsigned long nitems, bytes_after; + unsigned char *data = nullptr; + + XGetWindowProperty(display, window, prop, 0, ~0, False, AnyPropertyType, + &type, &format, &nitems, &bytes_after, &data); + if (data) { + std::string result((char*)data, nitems); + XFree(data); + return result; + } + } + return ""; + } +}; + +#endif diff --git a/libs/httpPage.hpp b/libs/httpPage.hpp index 220f4e7..4a39900 100644 --- a/libs/httpPage.hpp +++ b/libs/httpPage.hpp @@ -9641,12 +9641,13 @@ void parseHttpPage() var text = e.clipboardData.getData('text/plain') || window.clipboardData.getData('Text'); //console.log(text); if (webSocket != null && webSocket.readyState == 1) { + webSocket.send("P" + text); //update as soon as possible [make it server side] - for (var i = 0; i < text.length; i++) { - let key = getXkeyName(text[i]); - webSocket.send("K1" + key + "\0"); - await sleep(80); - } + // for (var i = 0; i < text.length; i++) { + // let key = getXkeyName(text[i]); + // webSocket.send("K1" + key + "\0"); + // await sleep(80); + // } } } function drawCanvas(data) { @@ -9692,7 +9693,7 @@ void parseHttpPage() return; } statusdiv.innerText = "connecting..."; - let ws = new WebSocket("ws://" + location.host); + let ws = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://` + location.host); webSocket = ws; ws.onerror = function (evt) { //console.log("WebSocket Error: ", e); diff --git a/libs/input.hpp b/libs/input.hpp index a0c28ce..e070189 100644 --- a/libs/input.hpp +++ b/libs/input.hpp @@ -18,6 +18,7 @@ #ifndef INPUT_HPP #define INPUT_HPP +#include "clipboard.hpp" #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include class XInputs { @@ -34,9 +36,10 @@ class XInputs void processInputs(char *data, int clinetSD); void queueInputs(char *data, int clinetSD); void dispatchEvents(); + Clipboard *clipboard; private: Display *display; - char events[30][100] = {0}; + char events[30][1024] = {0}; int eventCount = 0; }; @@ -49,6 +52,7 @@ void XInputs::queueInputs(char *data, int clinetSD) { if (this->eventCount < 30) { + std::cout << data << std::endl; strcpy(this->events[this->eventCount], data); this->eventCount++; } @@ -69,7 +73,7 @@ void XInputs::processInputs(char *data, int clientSD) { int len = strlen(data); int x = 0, y = 0, i = 1, x2 = 0, y2 = 0; - if (data[0] == 'C') + if (data[0] == 'C') //for click to x y pos { while (data[i] != 32 && i < len) x = x * 10 + data[i++] - 48; @@ -78,12 +82,12 @@ void XInputs::processInputs(char *data, int clientSD) y = y * 10 + data[i++] - 48; if (this->display == 0) return; - XTestFakeMotionEvent(this->display, -1, x, y, CurrentTime); + XTestFakeMotionEvent(this->display, DefaultScreen(this->display), x, y, CurrentTime); XTestFakeButtonEvent(this->display, 1, True, CurrentTime); XTestFakeButtonEvent(this->display, 1, False, CurrentTime); XFlush(this->display); } - else if (data[0] == 'M') + else if (data[0] == 'M') //only mouse pointer move to x y pos { while (data[i] != 32 && i < len) x = x * 10 + data[i++] - 48; @@ -92,10 +96,10 @@ void XInputs::processInputs(char *data, int clientSD) y = y * 10 + data[i++] - 48; if (this->display == 0) return; - XTestFakeMotionEvent(this->display, -1, x, y, CurrentTime); + XTestFakeMotionEvent(this->display,DefaultScreen(this->display), x, y, CurrentTime); XFlush(this->display); } - else if (data[0] == 'R') + else if (data[0] == 'R') // right click at x y pos { while (data[i] != 32 && i < len) x = x * 10 + data[i++] - 48; @@ -104,12 +108,12 @@ void XInputs::processInputs(char *data, int clientSD) y = y * 10 + data[i++] - 48; if (this->display == 0) return; - XTestFakeMotionEvent(this->display, -1, x, y, CurrentTime); + XTestFakeMotionEvent(this->display, DefaultScreen(this->display), x, y, CurrentTime); XTestFakeButtonEvent(this->display, 3, True, CurrentTime); XTestFakeButtonEvent(this->display, 3, False, CurrentTime); XFlush(this->display); } - else if (data[0] == 'D') + else if (data[0] == 'D') // drag from x y pos to x2 y2 pos { while (data[i] != 32 && i < len) x = x * 10 + data[i++] - 48; @@ -124,34 +128,34 @@ void XInputs::processInputs(char *data, int clientSD) y2 = y2 * 10 + data[i++] - 48; if (this->display == 0) return; - XTestFakeMotionEvent(this->display, -1, x, y, CurrentTime); + XTestFakeMotionEvent(this->display, DefaultScreen(this->display), x, y, CurrentTime); XTestFakeButtonEvent(this->display, 1, True, CurrentTime); - XTestFakeMotionEvent(this->display, -1, x2, y2, CurrentTime); + XTestFakeMotionEvent(this->display, DefaultScreen(this->display), x2, y2, CurrentTime); XFlush(this->display); } - else if (data[0] == 'S') + else if (data[0] == 'S') // mouse scroll { - if (data[1] == 'U') + if (data[1] == 'U') // scroll uo { XTestFakeButtonEvent(this->display, 4, 1, 0); XTestFakeButtonEvent(this->display, 4, 0, 70); } - else + else //scroll down { XTestFakeButtonEvent(this->display, 5, 1, 0); XTestFakeButtonEvent(this->display, 5, 0, 70); } } - else if (data[0] == 'K') + else if (data[0] == 'K') // keyboard key press { - if (data[1] == 49) + if (data[1] == 49) // single key press { int keycode = XKeysymToKeycode(this->display, XStringToKeysym(data + 2)); XTestFakeKeyEvent(this->display, keycode, True, CurrentTime); XTestFakeKeyEvent(this->display, keycode, False, CurrentTime); XFlush(this->display); } - else if (data[1] == 50) + else if (data[1] == 50) // combo key press { char buffer[15] = {0}; int i = 2; @@ -170,6 +174,20 @@ void XInputs::processInputs(char *data, int clientSD) XFlush(this->display); } } + else if (data[0] == 'P') // paste (ctrl + shift + v) + { + this->clipboard->setText(&data[1]); + int keycode = XKeysymToKeycode(this->display, XStringToKeysym("Control_L")); + int keycode2 = XKeysymToKeycode(this->display, XStringToKeysym("Shift_L")); + int keycode3 = XKeysymToKeycode(this->display, XStringToKeysym("v")); + XTestFakeKeyEvent(this->display, keycode, True, CurrentTime); + XTestFakeKeyEvent(this->display, keycode2, True, CurrentTime); + XTestFakeKeyEvent(this->display, keycode3, True, CurrentTime); + XTestFakeKeyEvent(this->display, keycode3, False, CurrentTime); + XTestFakeKeyEvent(this->display, keycode2, False, CurrentTime); + XTestFakeKeyEvent(this->display, keycode, False, CurrentTime); + XFlush(this->display); + } XFlush(this->display); } diff --git a/libs/websocket.hpp b/libs/websocket.hpp index 47d7cc8..71234a8 100644 --- a/libs/websocket.hpp +++ b/libs/websocket.hpp @@ -196,7 +196,7 @@ void Websocket::connections() { if (callBackMsg != NULL) { - char inputData[200]={0}; + char inputData[1024]={0}; decode(buffer , inputData); callBackMsg(inputData, i); }