From b3901807e87af4bd91bc00489c6349dc24b43d33 Mon Sep 17 00:00:00 2001 From: Michael Lotz Date: Sun, 4 Oct 2009 14:21:01 +0000 Subject: [PATCH] Adding RemoteDesktop client. It wraps a port forwarding ssh call to provide security and compression for the session. This allows to keep these layers separate from the actual protocol. This needs to be run from the command line, as it will request ssh auth there. After connecting it will export the needed env variable and run the specified shell (Terminal by default) on the remote host to make it connect to the local client app. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@33418 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/Jamfile | 1 + src/apps/remotedesktop/Jamfile | 25 + src/apps/remotedesktop/RemoteDesktop.cpp | 176 +++ src/apps/remotedesktop/RemoteDesktop.rdef | 15 + src/apps/remotedesktop/RemoteView.cpp | 1249 +++++++++++++++++++++ src/apps/remotedesktop/RemoteView.h | 86 ++ 6 files changed, 1552 insertions(+) create mode 100644 src/apps/remotedesktop/Jamfile create mode 100644 src/apps/remotedesktop/RemoteDesktop.cpp create mode 100644 src/apps/remotedesktop/RemoteDesktop.rdef create mode 100644 src/apps/remotedesktop/RemoteView.cpp create mode 100644 src/apps/remotedesktop/RemoteView.h diff --git a/src/apps/Jamfile b/src/apps/Jamfile index fbd2228ea4..14af4180cf 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -42,6 +42,7 @@ HaikuSubInclude poorman ; HaikuSubInclude powerstatus ; HaikuSubInclude processcontroller ; HaikuSubInclude pulse ; +HaikuSubInclude remotedesktop ; HaikuSubInclude resedit ; HaikuSubInclude screenshot ; HaikuSubInclude showimage ; diff --git a/src/apps/remotedesktop/Jamfile b/src/apps/remotedesktop/Jamfile new file mode 100644 index 0000000000..d82472d30a --- /dev/null +++ b/src/apps/remotedesktop/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src apps remotedesktop ; + +local defines = [ FDefines CLIENT_COMPILE ] ; +local serverDir = [ FDirName $(HAIKU_TOP) src servers app drawing remote ] ; + +SubDirC++Flags $(defines) ; + +UsePrivateHeaders shared ; +UseHeaders $(serverDir) ; + +Application RemoteDesktop : + RemoteDesktop.cpp + RemoteMessage.cpp + RemoteView.cpp + + NetReceiver.cpp + NetSender.cpp + StreamingRingBuffer.cpp + + : be bnetapi $(TARGET_LIBSUPC++) + : RemoteDesktop.rdef +; + +SEARCH on [ FGristFiles NetReceiver.cpp NetSender.cpp RemoteMessage.cpp + StreamingRingBuffer.cpp ] = $(serverDir) ; diff --git a/src/apps/remotedesktop/RemoteDesktop.cpp b/src/apps/remotedesktop/RemoteDesktop.cpp new file mode 100644 index 0000000000..c30612d0c6 --- /dev/null +++ b/src/apps/remotedesktop/RemoteDesktop.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2009, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Michael Lotz + */ + +#include +#include +#include + +#include "RemoteView.h" + +#include +#include +#include +#include +#include +#include + + +void +print_usage(const char *app) +{ + printf("usage:\t%s --listenOnly [-p ]\n", app); + printf("\t%s [-p ] [-s ] [-c ]\n", + app); +} + + +int +main(int argc, char *argv[]) +{ + if (argc < 2) { + print_usage(argv[0]); + return 1; + } + + bool listenOnly = false; + uint32 listenPort = 10900; + uint32 sshPort = 22; + const char *command = "/system/apps/Terminal"; + + for (int32 i = 2; i < argc; i++) { + if (strcmp(argv[i], "-p") == 0) { + if (argc < i + 1 || sscanf(argv[i + 1], "%lu", &listenPort) != 1) { + print_usage(argv[0]); + return 2; + } + + i++; + continue; + } + + if (strcmp(argv[i], "-s") == 0) { + if (argc < i + 1 || sscanf(argv[i + 1], "%lu", &sshPort) != 1) { + print_usage(argv[0]); + return 2; + } + + i++; + continue; + } + + if (strcmp(argv[i], "-c") == 0) { + if (argc < i + 1) { + print_usage(argv[0]); + return 2; + } + + i++; + command = argv[i]; + continue; + } + + if (strcmp(argv[i], "--listenOnly") == 0) { + listenOnly = true; + continue; + } + + print_usage(argv[0]); + return 2; + } + + pid_t sshPID = -1; + if (!listenOnly) { + char shellCommand[4096]; + snprintf(shellCommand, sizeof(shellCommand), + "echo connected; export TARGET_SCREEN=localhost:%lu; %s\n", + listenPort, command); + + int pipes[4]; + if (pipe(&pipes[0]) != 0 || pipe(&pipes[2]) != 0) { + printf("failed to create redirection pipes\n"); + return 3; + } + + sshPID = fork(); + if (sshPID < 0) { + printf("failed to fork ssh process\n"); + return 3; + } + + if (sshPID == 0) { + // child code, redirect std* and execute ssh + close(STDOUT_FILENO); + close(STDIN_FILENO); + dup2(pipes[1], STDOUT_FILENO); + dup2(pipes[1], STDERR_FILENO); + dup2(pipes[2], STDIN_FILENO); + for (int32 i = 0; i < 4; i++) + close(pipes[i]); + + char localRedirect[50]; + sprintf(localRedirect, "localhost:%lu:localhost:%lu", + listenPort + 1, listenPort + 1); + + char remoteRedirect[50]; + sprintf(remoteRedirect, "localhost:%lu:localhost:%lu", + listenPort, listenPort); + + char portNumber[10]; + sprintf(portNumber, "%lu", sshPort); + + int result = execl("ssh", "-C", "-L", localRedirect, + "-R", remoteRedirect, "-p", portNumber, argv[1], + shellCommand, NULL); + + // we don't get here unless there was an error in executing + printf("failed to execute ssh process in child\n"); + return result; + } else { + close(pipes[1]); + close(pipes[2]); + + char buffer[10]; + read(pipes[0], buffer, sizeof(buffer)); + // block until connected/error message from ssh + } + } + + BApplication app("application/x-vnd.Haiku-RemoteDesktop"); + BScreen screen; + BWindow *window = new(std::nothrow) BWindow(screen.Frame(), "RemoteDesktop", + B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE); + + if (window == NULL) { + printf("no memory to allocate window\n"); + return 4; + } + + RemoteView *view = new(std::nothrow) RemoteView(window->Bounds(), + listenPort); + if (view == NULL) { + printf("no memory to allocate remote view\n"); + return 4; + } + + status_t init = view->InitCheck(); + if (init != B_OK) { + printf("initialization of remote view failed: %s\n", strerror(init)); + delete view; + return 5; + } + + window->AddChild(view); + view->MakeFocus(); + window->Show(); + app.Run(); + + if (sshPID >= 0) + kill(sshPID, SIGINT); + + return 0; +} diff --git a/src/apps/remotedesktop/RemoteDesktop.rdef b/src/apps/remotedesktop/RemoteDesktop.rdef new file mode 100644 index 0000000000..3079511332 --- /dev/null +++ b/src/apps/remotedesktop/RemoteDesktop.rdef @@ -0,0 +1,15 @@ +resource app_signature "application/x-vnd.Haiku-RemoteDesktop"; + +resource app_version { + major = 0, + middle = 1, + minor = 0, + + variety = B_APPV_ALPHA, + internal = 0, + + short_info = "RemoteDesktop", + long_info = "RemoteDesktop ©2009 Haiku, Inc." +}; + +resource app_flags B_MULTIPLE_LAUNCH; diff --git a/src/apps/remotedesktop/RemoteView.cpp b/src/apps/remotedesktop/RemoteView.cpp new file mode 100644 index 0000000000..da509875fd --- /dev/null +++ b/src/apps/remotedesktop/RemoteView.cpp @@ -0,0 +1,1249 @@ +/* + * Copyright 2009, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Michael Lotz + */ + +#include "NetReceiver.h" +#include "NetSender.h" +#include "RemoteMessage.h" +#include "RemoteView.h" +#include "StreamingRingBuffer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +static const uint8 kCursorData[] = { 16 /* size, 16x16 */, + 1 /* depth, 1 bit per pixel */, 0, 0, /* hot spot at 0, 0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +#define TRACE(x...) /*printf("RemoteView: "x)*/ +#define TRACE_ALWAYS(x...) printf("RemoteView: "x) +#define TRACE_ERROR(x...) printf("RemoteView: "x) + + +typedef struct engine_state { + uint32 token; + BView * view; + ::pattern pattern; + BRegion clipping_region; + float pen_size; + bool sync_drawing; +} engine_state; + + +RemoteView::RemoteView(BRect frame, uint16 listenPort) + : + BView(frame, "RemoteView", B_FOLLOW_NONE, B_WILL_DRAW), + fInitStatus(B_NO_INIT), + fIsConnected(false), + fReceiveBuffer(NULL), + fSendBuffer(NULL), + fReceiveEndpoint(NULL), + fSendEndpoint(NULL), + fReceiver(NULL), + fSender(NULL), + fStopThread(false), + fOffscreenBitmap(NULL), + fOffscreen(NULL), + fViewCursor(kCursorData), + fCursorBitmap(NULL), + fCursorVisible(false) +{ + fReceiveBuffer = new(std::nothrow) StreamingRingBuffer(16 * 1024); + if (fReceiveBuffer == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fInitStatus = fReceiveBuffer->InitCheck(); + if (fInitStatus != B_OK) + return; + + fSendBuffer = new(std::nothrow) StreamingRingBuffer(16 * 1024); + if (fSendBuffer == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fInitStatus = fSendBuffer->InitCheck(); + if (fInitStatus != B_OK) + return; + + fReceiveEndpoint = new(std::nothrow) BNetEndpoint(); + if (fReceiveEndpoint == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fInitStatus = fReceiveEndpoint->Bind(listenPort); + if (fInitStatus != B_OK) + return; + + fReceiver = new(std::nothrow) NetReceiver(fReceiveEndpoint, fReceiveBuffer); + if (fReceiver == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fSendEndpoint = new(std::nothrow) BNetEndpoint(); + if (fSendEndpoint == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fSender = new(std::nothrow) NetSender(fSendEndpoint, fSendBuffer); + if (fSender == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + BRect bounds = frame.OffsetToCopy(0, 0); + fOffscreenBitmap = new(std::nothrow) BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, + B_RGB32); + if (fOffscreenBitmap == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fOffscreen = new(std::nothrow) BView(bounds, "offscreen remote view", + B_FOLLOW_NONE, B_WILL_DRAW); + if (fOffscreen == NULL) { + fInitStatus = B_NO_MEMORY; + return; + } + + fOffscreenBitmap->AddChild(fOffscreen); + fOffscreen->SetDrawingMode(B_OP_COPY); + + fDrawThread = spawn_thread(&_DrawEntry, "draw thread", B_NORMAL_PRIORITY, + this); + if (fDrawThread < 0) { + fInitStatus = fDrawThread; + return; + } + + resume_thread(fDrawThread); +} + + +RemoteView::~RemoteView() +{ + fStopThread = true; + + delete fReceiver; + delete fReceiveBuffer; + + delete fSendBuffer; + delete fSender; + + delete fReceiveEndpoint; + delete fSendEndpoint; + + delete fOffscreenBitmap; + delete fCursorBitmap; + + int32 result; + wait_for_thread(fDrawThread, &result); +} + + +status_t +RemoteView::InitCheck() +{ + return fInitStatus; +} + + +void +RemoteView::AttachedToWindow() +{ + SetViewColor(B_TRANSPARENT_COLOR); + SetViewCursor(&fViewCursor); +} + + +void +RemoteView::Draw(BRect updateRect) +{ + SetDrawingMode(B_OP_COPY); + fOffscreenBitmap->Lock(); + fOffscreen->Sync(); + + DrawBitmap(fOffscreenBitmap, updateRect, updateRect); + + if (fCursorVisible && fCursorBitmap != NULL + && fCursorFrame.Intersects(updateRect)) { + DrawBitmap(fOffscreenBitmap, fCursorFrame, fCursorFrame); + SetDrawingMode(B_OP_ALPHA); + DrawBitmap(fCursorBitmap, fCursorFrame.LeftTop()); + } + + fOffscreenBitmap->Unlock(); +} + + +void +RemoteView::MouseMoved(BPoint where, uint32 code, const BMessage *dragMessage) +{ + if (!fIsConnected) + return; + + _SendMouseMessage(RP_MOUSE_MOVED, where); +} + + +void +RemoteView::MouseDown(BPoint where) +{ + if (!fIsConnected) + return; + + _SendMouseMessage(RP_MOUSE_DOWN, where); +} + + +void +RemoteView::MouseUp(BPoint where) +{ + if (!fIsConnected) + return; + + _SendMouseMessage(RP_MOUSE_UP, where); +} + + +void +RemoteView::KeyDown(const char *bytes, int32 numBytes) +{ + if (!fIsConnected) + return; + + _SendKeyMessage(RP_KEY_DOWN, bytes, numBytes); +} + + +void +RemoteView::KeyUp(const char *bytes, int32 numBytes) +{ + if (!fIsConnected) + return; + + _SendKeyMessage(RP_KEY_UP, bytes, numBytes); +} + + +void +RemoteView::MessageReceived(BMessage *message) +{ + if (!fIsConnected) { + BView::MessageReceived(message); + return; + } + + switch (message->what) { + case B_UNMAPPED_KEY_DOWN: + case B_UNMAPPED_KEY_UP: + // these are easily repeated and then cause a flood of messages + // so we might not want them. + break; + + case B_MODIFIERS_CHANGED: + { + uint32 modifiers = 0; + message->FindInt32("modifiers", (int32 *)&modifiers); + RemoteMessage message(NULL, fSendBuffer); + message.Start(RP_MODIFIERS_CHANGED); + message.Add(modifiers); + break; + } + + case B_MOUSE_WHEEL_CHANGED: + { + float xDelta, yDelta; + if (message->FindFloat("be:wheel_delta_x", &xDelta) != B_OK) + xDelta = 0; + if (message->FindFloat("be:wheel_delta_y", &yDelta) != B_OK) + yDelta = 0; + + RemoteMessage message(NULL, fSendBuffer); + message.Start(RP_MOUSE_WHEEL_CHANGED); + message.Add(xDelta); + message.Add(yDelta); + break; + } + } + + BView::MessageReceived(message); +} + + +void +RemoteView::_SendMouseMessage(uint16 code, BPoint where) +{ + RemoteMessage message(NULL, fSendBuffer); + message.Start(code); + message.Add(where); + + if (code == RP_MOUSE_MOVED) + return; + + BMessage *event = Window()->CurrentMessage(); + + int32 buttons = 0; + event->FindInt32("buttons", &buttons); + message.Add(buttons); + + if (code == RP_MOUSE_DOWN) + return; + + int32 clicks; + event->FindInt32("clicks", &clicks); + message.Add(clicks); +} + + +void +RemoteView::_SendKeyMessage(uint16 code, const char *bytes, int32 numBytes) +{ + RemoteMessage message(NULL, fSendBuffer); + message.Start(code); + message.Add(numBytes); + message.AddList(bytes, numBytes); + + BMessage *event = Window()->CurrentMessage(); + + int32 rawChar, key; + event->FindInt32("raw_char", &rawChar); + event->FindInt32("key", &key); + + message.Add(rawChar); + message.Add(key); +} + + +int +RemoteView::_StateCompareByKey(const uint32 *key, const engine_state *state) +{ + if (state->token == *key) + return 0; + + if (state->token < *key) + return -1; + + return 1; +} + + +void +RemoteView::_CreateState(uint32 token) +{ + int32 index = fStates.BinarySearchIndexByKey(token, &_StateCompareByKey); + if (index >= 0) { + TRACE_ERROR("state for token %lu already in list\n", token); + return; + } + + engine_state *state = new(std::nothrow) engine_state; + if (state == NULL) { + TRACE_ERROR("failed to allocate engine state\n"); + return; + } + + fOffscreenBitmap->Lock(); + BView *offscreen = new(std::nothrow) BView(fOffscreenBitmap->Bounds(), + "offscreen remote view", B_FOLLOW_NONE, B_WILL_DRAW); + if (offscreen == NULL) { + TRACE_ERROR("failed to allocate offscreen view\n"); + fOffscreenBitmap->Unlock(); + delete state; + return; + } + + fOffscreenBitmap->AddChild(offscreen); + fOffscreenBitmap->Unlock(); + + state->token = token; + state->view = offscreen; + state->pattern = B_SOLID_HIGH; + state->clipping_region.MakeEmpty(); + state->pen_size = 0; + state->sync_drawing = true; + + fStates.AddItem(state, -index - 1); +} + + +void +RemoteView::_DeleteState(uint32 token) +{ + int32 index = fStates.BinarySearchIndexByKey(token, &_StateCompareByKey); + if (index < 0) + return; + + engine_state *state = fStates.RemoveItemAt(index); + + fOffscreenBitmap->RemoveChild(state->view); + delete state->view; + delete state; +} + + +engine_state * +RemoteView::_FindState(uint32 token) +{ + return fStates.BinarySearchByKey(token, &_StateCompareByKey); +} + + +int32 +RemoteView::_DrawEntry(void *data) +{ + ((RemoteView *)data)->_DrawThread(); + return 0; +} + + +void +RemoteView::_DrawThread() +{ + RemoteMessage reply(NULL, fSendBuffer); + RemoteMessage message(fReceiveBuffer, NULL); + + // cursor + BPoint cursorHotSpot(0, 0); + + while (!fStopThread) { + uint16 code; + status_t status = message.NextMessage(code); + if (status != B_OK) { + TRACE_ERROR("failed to read message from receiver\n"); + break; + } + + TRACE("code %u with %ld bytes data\n", code, message.DataLeft()); + + BAutolock locker(this->Looper()); + if (!locker.IsLocked()) + break; + + // handle stuff that doesn't go to a specicifc engine + switch (code) { + case RP_INIT_CONNECTION: + { + uint16 port; + status_t result = message.Read(port); + if (result != B_OK) { + TRACE_ERROR("failed to read remote port\n"); + continue; + } + + BNetEndpoint *endpoint = fReceiver->Endpoint(); + if (endpoint == NULL) { + TRACE_ERROR("receiver not connected anymore\n"); + continue; + } + + in_addr remoteHost; + char hostName[MAXHOSTNAMELEN + 1]; + BNetAddress address(endpoint->RemoteAddr()); + address.GetAddr(remoteHost); + address.GetAddr(hostName, NULL); + address.SetTo(remoteHost, port); + + TRACE("connecting to host \"%s\" port %u\n", hostName, port); + result = fSendEndpoint->Connect(address); + if (result != B_OK) { + TRACE_ERROR("failed to connect to host \"%s\" port %u\n", + hostName, port); + continue; + } + + BRect bounds = fOffscreenBitmap->Bounds(); + reply.Start(RP_UPDATE_DISPLAY_MODE); + reply.Add(bounds.IntegerWidth() + 1); + reply.Add(bounds.IntegerHeight() + 1); + if (reply.Flush() == B_OK) + fIsConnected = true; + + continue; + } + + case RP_CLOSE_CONNECTION: + { + be_app->PostMessage(B_QUIT_REQUESTED); + continue; + } + + case RP_CREATE_STATE: + case RP_DELETE_STATE: + { + uint32 token; + message.Read(token); + + if (code == RP_CREATE_STATE) + _CreateState(token); + else + _DeleteState(token); + + continue; + } + + case RP_SET_CURSOR: + { + BBitmap *bitmap; + BPoint oldHotSpot = cursorHotSpot; + message.Read(cursorHotSpot); + if (message.ReadBitmap(&bitmap) != B_OK) + continue; + + delete fCursorBitmap; + fCursorBitmap = bitmap; + + Invalidate(fCursorFrame); + + BRect bounds = fCursorBitmap->Bounds(); + fCursorFrame.right = fCursorFrame.left + + bounds.IntegerWidth() + 1; + fCursorFrame.bottom = fCursorFrame.bottom + + bounds.IntegerHeight() + 1; + + fCursorFrame.OffsetBy(oldHotSpot - cursorHotSpot); + + Invalidate(fCursorFrame); + continue; + } + + case RP_SET_CURSOR_VISIBLE: + { + bool wasVisible = fCursorVisible; + message.Read(fCursorVisible); + if (wasVisible != fCursorVisible) + Invalidate(fCursorFrame); + continue; + } + + case RP_MOVE_CURSOR_TO: + { + BPoint position; + message.Read(position); + + if (fCursorVisible) + Invalidate(fCursorFrame); + + fCursorFrame.OffsetTo(position - cursorHotSpot); + + Invalidate(fCursorFrame); + continue; + } + + case RP_INVALIDATE_RECT: + { + BRect rect; + if (message.Read(rect) != B_OK) + continue; + + Invalidate(rect); + continue; + } + + case RP_INVALIDATE_REGION: + { + BRegion region; + if (message.ReadRegion(region) != B_OK) + continue; + + Invalidate(®ion); + continue; + } + + case RP_FILL_REGION_COLOR_NO_CLIPPING: + { + BRegion region; + rgb_color color; + + message.ReadRegion(region); + if (message.Read(color) != B_OK) + continue; + + fOffscreen->LockLooper(); + fOffscreen->SetHighColor(color); + fOffscreen->FillRegion(®ion); + fOffscreen->UnlockLooper(); + Invalidate(®ion); + continue; + } + + case RP_COPY_RECT_NO_CLIPPING: + { + int32 xOffset, yOffset; + BRect rect; + + message.Read(xOffset); + message.Read(yOffset); + if (message.Read(rect) != B_OK) + continue; + + BRect dest = rect.OffsetByCopy(xOffset, yOffset); + fOffscreen->LockLooper(); + fOffscreen->CopyBits(rect, dest); + fOffscreen->UnlockLooper(); + continue; + } + } + + uint32 token; + message.Read(token); + + engine_state *state = _FindState(token); + if (state == NULL) { + TRACE_ERROR("didn't find state for token %lu\n", token); + continue; + } + + BView *offscreen = state->view; + ::pattern &pattern = state->pattern; + BRegion &clippingRegion = state->clipping_region; + float &penSize = state->pen_size; + bool &syncDrawing = state->sync_drawing; + BRegion invalidRegion; + + BAutolock offscreenLocker(offscreen->Looper()); + if (!offscreenLocker.IsLocked()) + break; + + switch (code) { + case RP_ENABLE_SYNC_DRAWING: + syncDrawing = true; + continue; + + case RP_DISABLE_SYNC_DRAWING: + syncDrawing = false; + continue; + + case RP_SET_OFFSETS: + { + int32 xOffset, yOffset; + message.Read(xOffset); + if (message.Read(yOffset) != B_OK) + continue; + + offscreen->MovePenTo(xOffset, yOffset); + break; + } + + case RP_SET_HIGH_COLOR: + case RP_SET_LOW_COLOR: + { + rgb_color color; + if (message.Read(color) != B_OK) + continue; + + if (code == RP_SET_HIGH_COLOR) + offscreen->SetHighColor(color); + else + offscreen->SetLowColor(color); + + break; + } + + case RP_SET_PEN_SIZE: + { + float newPenSize; + if (message.Read(newPenSize) != B_OK) + continue; + + offscreen->SetPenSize(newPenSize); + penSize = newPenSize / 2; + break; + } + + case RP_SET_STROKE_MODE: + { + cap_mode capMode; + join_mode joinMode; + float miterLimit; + + message.Read(capMode); + message.Read(joinMode); + if (message.Read(miterLimit) != B_OK) + continue; + + offscreen->SetLineMode(capMode, joinMode, miterLimit); + break; + } + + case RP_SET_BLENDING_MODE: + { + source_alpha sourceAlpha; + alpha_function alphaFunction; + + message.Read(sourceAlpha); + if (message.Read(alphaFunction) != B_OK) + continue; + + offscreen->SetBlendingMode(sourceAlpha, alphaFunction); + break; + } + + case RP_SET_PATTERN: + { + if (message.Read(pattern) != B_OK) + continue; + break; + } + + case RP_SET_DRAWING_MODE: + { + drawing_mode drawingMode; + if (message.Read(drawingMode) != B_OK) + continue; + + offscreen->SetDrawingMode(drawingMode); + break; + } + + case RP_SET_FONT: + { + BFont font; + if (message.ReadFontState(font) != B_OK) + continue; + + offscreen->SetFont(&font); + break; + } + + case RP_CONSTRAIN_CLIPPING_REGION: + { + if (message.ReadRegion(clippingRegion) != B_OK) + continue; + + offscreen->ConstrainClippingRegion(&clippingRegion); + break; + } + + case RP_INVERT_RECT: + { + BRect rect; + if (message.Read(rect) != B_OK) + continue; + + offscreen->InvertRect(rect); + invalidRegion.Include(rect); + break; + } + + case RP_DRAW_BITMAP: + { + BBitmap *bitmap; + BRect bitmapRect, viewRect; + uint32 options; + + message.Read(bitmapRect); + message.Read(viewRect); + message.Read(options); + if (message.ReadBitmap(&bitmap) != B_OK || bitmap == NULL) + continue; + + offscreen->DrawBitmap(bitmap, bitmapRect, viewRect, options); + invalidRegion.Include(viewRect); + delete bitmap; + break; + } + + case RP_STROKE_ARC: + case RP_FILL_ARC: + case RP_FILL_ARC_GRADIENT: + { + BRect rect; + float angle, span; + + message.Read(rect); + message.Read(angle); + if (message.Read(span) != B_OK) + continue; + + if (code == RP_STROKE_ARC) { + offscreen->StrokeArc(rect, angle, span, pattern); + rect.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_ARC) + offscreen->FillArc(rect, angle, span, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillArc(rect, angle, span, *gradient); + delete gradient; + } + + invalidRegion.Include(rect); + break; + } + + case RP_STROKE_BEZIER: + case RP_FILL_BEZIER: + case RP_FILL_BEZIER_GRADIENT: + { + BPoint points[4]; + if (message.ReadList(points, 4) != B_OK) + continue; + + BRect bounds = _BuildInvalidateRect(points, 4); + if (code == RP_STROKE_BEZIER) { + offscreen->StrokeBezier(points, pattern); + bounds.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_BEZIER) + offscreen->FillBezier(points, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillBezier(points, *gradient); + delete gradient; + } + + invalidRegion.Include(bounds); + break; + } + + case RP_STROKE_ELLIPSE: + case RP_FILL_ELLIPSE: + case RP_FILL_ELLIPSE_GRADIENT: + { + BRect rect; + if (message.Read(rect) != B_OK) + continue; + + if (code == RP_STROKE_ELLIPSE) { + offscreen->StrokeEllipse(rect, pattern); + rect.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_ELLIPSE) + offscreen->FillEllipse(rect, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillEllipse(rect, *gradient); + delete gradient; + } + + invalidRegion.Include(rect); + break; + } + + case RP_STROKE_POLYGON: + case RP_FILL_POLYGON: + case RP_FILL_POLYGON_GRADIENT: + { + BRect bounds; + bool closed; + int32 numPoints; + + message.Read(bounds); + message.Read(closed); + if (message.Read(numPoints) != B_OK) + continue; + + BPoint points[numPoints]; + for (int32 i = 0; i < numPoints; i++) + message.Read(points[i]); + + if (code == RP_STROKE_POLYGON) { + offscreen->StrokePolygon(points, numPoints, bounds, closed, + pattern); + bounds.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_POLYGON) + offscreen->FillPolygon(points, numPoints, bounds, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillPolygon(points, numPoints, bounds, + *gradient); + delete gradient; + } + + invalidRegion.Include(bounds); + break; + } + + case RP_STROKE_RECT: + case RP_FILL_RECT: + case RP_FILL_RECT_GRADIENT: + { + BRect rect; + if (message.Read(rect) != B_OK) + continue; + + if (code == RP_STROKE_RECT) { + offscreen->StrokeRect(rect, pattern); + rect.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_RECT) + offscreen->FillRect(rect, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillRect(rect, *gradient); + delete gradient; + } + + invalidRegion.Include(rect); + break; + } + + case RP_STROKE_ROUND_RECT: + case RP_FILL_ROUND_RECT: + case RP_FILL_ROUND_RECT_GRADIENT: + { + BRect rect; + float xRadius, yRadius; + + message.Read(rect); + message.Read(xRadius); + if (message.Read(yRadius) != B_OK) + continue; + + if (code == RP_STROKE_ROUND_RECT) { + offscreen->StrokeRoundRect(rect, xRadius, yRadius, + pattern); + rect.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_ROUND_RECT) + offscreen->FillRoundRect(rect, xRadius, yRadius, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillRoundRect(rect, xRadius, yRadius, + *gradient); + delete gradient; + } + + invalidRegion.Include(rect); + break; + } + + case RP_STROKE_SHAPE: + case RP_FILL_SHAPE: + case RP_FILL_SHAPE_GRADIENT: + { + BRect bounds; + int32 opCount, pointCount; + + message.Read(bounds); + if (message.Read(opCount) != B_OK) + continue; + + BMessage archive; + for (int32 i = 0; i < opCount; i++) { + int32 op; + message.Read(op); + archive.AddInt32("ops", op); + } + + if (message.Read(pointCount) != B_OK) + continue; + + for (int32 i = 0; i < pointCount; i++) { + BPoint point; + message.Read(point); + archive.AddPoint("pts", point); + } + + // the shape is in absolute coordinates + offscreen->MovePenTo(0, 0); + + BShape shape(&archive); + if (code == RP_STROKE_SHAPE) { + offscreen->StrokeShape(&shape, pattern); + bounds.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_SHAPE) + offscreen->FillShape(&shape, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillShape(&shape, *gradient); + delete gradient; + } + + invalidRegion.Include(bounds); + break; + } + + case RP_STROKE_TRIANGLE: + case RP_FILL_TRIANGLE: + case RP_FILL_TRIANGLE_GRADIENT: + { + BRect bounds; + BPoint points[3]; + + message.ReadList(points, 3); + if (message.Read(bounds) != B_OK) + continue; + + if (code == RP_STROKE_TRIANGLE) { + offscreen->StrokeTriangle(points[0], points[1], points[2], + bounds, pattern); + bounds.InsetBy(-penSize, -penSize); + } else if (code == RP_FILL_TRIANGLE) { + offscreen->FillTriangle(points[0], points[1], points[2], + bounds, pattern); + } else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillTriangle(points[0], points[1], points[2], + bounds, *gradient); + delete gradient; + } + + invalidRegion.Include(bounds); + break; + } + + case RP_STROKE_LINE: + { + BPoint points[2]; + if (message.ReadList(points, 2) != B_OK) + continue; + + offscreen->StrokeLine(points[0], points[1], pattern); + + BRect bounds = _BuildInvalidateRect(points, 2); + invalidRegion.Include(bounds.InsetBySelf(-penSize, -penSize)); + break; + } + + case RP_STROKE_LINE_ARRAY: + { + int32 numLines; + if (message.Read(numLines) != B_OK) + continue; + + BRect bounds; + offscreen->BeginLineArray(numLines); + for (int32 i = 0; i < numLines; i++) { + rgb_color color; + BPoint start, end; + message.ReadArrayLine(start, end, color); + offscreen->AddLine(start, end, color); + + bounds.left = min_c(bounds.left, min_c(start.x, end.x)); + bounds.top = min_c(bounds.top, min_c(start.y, end.y)); + bounds.right = max_c(bounds.right, max_c(start.x, end.x)); + bounds.bottom = max_c(bounds.bottom, max_c(start.y, end.y)); + } + + offscreen->EndLineArray(); + invalidRegion.Include(bounds); + break; + } + + case RP_FILL_REGION: + case RP_FILL_REGION_GRADIENT: + { + BRegion region; + if (message.ReadRegion(region) != B_OK) + continue; + + if (code == RP_FILL_REGION) + offscreen->FillRegion(®ion, pattern); + else { + BGradient *gradient; + if (message.ReadGradient(&gradient) != B_OK) + continue; + + offscreen->FillRegion(®ion, *gradient); + delete gradient; + } + + invalidRegion.Include(®ion); + break; + } + + case RP_STROKE_POINT_COLOR: + { + BPoint point; + rgb_color color; + + message.Read(point); + if (message.Read(color) != B_OK) + continue; + + rgb_color oldColor = offscreen->HighColor(); + offscreen->SetHighColor(color); + offscreen->StrokeLine(point, point); + offscreen->SetHighColor(oldColor); + + invalidRegion.Include( + BRect(point, point).InsetBySelf(-penSize, -penSize)); + break; + } + + case RP_STROKE_LINE_1PX_COLOR: + { + BPoint points[2]; + rgb_color color; + + message.ReadList(points, 2); + if (message.Read(color) != B_OK) + continue; + + float oldSize = offscreen->PenSize(); + rgb_color oldColor = offscreen->HighColor(); + drawing_mode oldMode = offscreen->DrawingMode(); + offscreen->SetPenSize(1); + offscreen->SetHighColor(color); + offscreen->SetDrawingMode(B_OP_OVER); + + offscreen->StrokeLine(points[0], points[1]); + + offscreen->SetDrawingMode(oldMode); + offscreen->SetHighColor(oldColor); + offscreen->SetPenSize(oldSize); + + invalidRegion.Include(_BuildInvalidateRect(points, 2)); + break; + } + + case RP_STROKE_RECT_1PX_COLOR: + case RP_FILL_RECT_COLOR: + { + BRect rect; + rgb_color color; + + message.Read(rect); + if (message.Read(color) != B_OK) + continue; + + rgb_color oldColor = offscreen->HighColor(); + offscreen->SetHighColor(color); + + if (code == RP_STROKE_RECT_1PX_COLOR) { + float oldSize = PenSize(); + offscreen->SetPenSize(1); + offscreen->StrokeRect(rect); + offscreen->SetPenSize(oldSize); + } else + offscreen->FillRect(rect); + + offscreen->SetHighColor(oldColor); + invalidRegion.Include(rect); + break; + } + + case RP_DRAW_STRING: + { + BPoint point; + size_t length; + char *string; + bool hasDelta; + + message.Read(point); + message.ReadString(&string, length); + if (message.Read(hasDelta) != B_OK) { + free(string); + continue; + } + + if (hasDelta) { + escapement_delta delta[length]; + message.ReadList(delta, length); + offscreen->DrawString(string, point, delta); + } else + offscreen->DrawString(string, point); + + free(string); + reply.Start(RP_DRAW_STRING_RESULT); + reply.Add(token); + reply.Add(offscreen->PenLocation()); + reply.Flush(); + + font_height height; + offscreen->GetFontHeight(&height); + + BRect bounds(point, offscreen->PenLocation()); + bounds.top -= height.ascent; + bounds.bottom += height.descent; + invalidRegion.Include(bounds); + break; + } + + case RP_READ_BITMAP: + { + BRect bounds; + bool drawCursor; + + message.Read(bounds); + if (message.Read(drawCursor) != B_OK) + continue; + + // TODO: support the drawCursor flag + BBitmap bitmap(bounds, B_BITMAP_NO_SERVER_LINK, B_RGB32); + bitmap.ImportBits(fOffscreenBitmap, bounds.LeftTop(), + BPoint(0, 0), bounds.IntegerWidth() + 1, + bounds.IntegerHeight() + 1); + + reply.Start(RP_READ_BITMAP_RESULT); + reply.Add(token); + reply.AddBitmap(&bitmap); + reply.Flush(); + break; + } + + default: + TRACE_ERROR("unknown protocol code: %u\n", code); + break; + } + + if (syncDrawing) { + offscreen->Sync(); + Invalidate(&invalidRegion); + } + } +} + + +BRect +RemoteView::_BuildInvalidateRect(BPoint *points, int32 pointCount) +{ + BRect bounds(1000000, 1000000, 0, 0); + for (int32 i = 0; i < pointCount; i++) { + bounds.left = min_c(bounds.left, points[i].x); + bounds.top = min_c(bounds.top, points[i].y); + bounds.right = max_c(bounds.right, points[i].x); + bounds.bottom = max_c(bounds.bottom, points[i].y); + } + + return bounds; +} diff --git a/src/apps/remotedesktop/RemoteView.h b/src/apps/remotedesktop/RemoteView.h new file mode 100644 index 0000000000..8ce29b149d --- /dev/null +++ b/src/apps/remotedesktop/RemoteView.h @@ -0,0 +1,86 @@ +/* + * Copyright 2009, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Michael Lotz + */ +#ifndef REMOTE_VIEW_H +#define REMOTE_VIEW_H + +#include +#include +#include +#include + +class BBitmap; +class NetReceiver; +class NetSender; +class StreamingRingBuffer; + +struct engine_state; + +class RemoteView : public BView { +public: + RemoteView(BRect frame, uint16 listenPort); +virtual ~RemoteView(); + + status_t InitCheck(); + +virtual void AttachedToWindow(); + +virtual void Draw(BRect updateRect); + +virtual void MouseMoved(BPoint where, uint32 code, + const BMessage *dragMessage); +virtual void MouseDown(BPoint where); +virtual void MouseUp(BPoint where); + +virtual void KeyDown(const char *bytes, int32 numBytes); +virtual void KeyUp(const char *bytes, int32 numBytes); + +virtual void MessageReceived(BMessage *message); + +private: + void _SendMouseMessage(uint16 code, + BPoint where); + void _SendKeyMessage(uint16 code, + const char *bytes, int32 numBytes); + +static int _StateCompareByKey(const uint32 *key, + const engine_state *state); + void _CreateState(uint32 token); + void _DeleteState(uint32 token); + engine_state * _FindState(uint32 token); + +static int32 _DrawEntry(void *data); + void _DrawThread(); + + BRect _BuildInvalidateRect(BPoint *points, + int32 pointCount); + + status_t fInitStatus; + bool fIsConnected; + + StreamingRingBuffer * fReceiveBuffer; + StreamingRingBuffer * fSendBuffer; + BNetEndpoint * fReceiveEndpoint; + BNetEndpoint * fSendEndpoint; + NetReceiver * fReceiver; + NetSender * fSender; + + bool fStopThread; + thread_id fDrawThread; + + BBitmap * fOffscreenBitmap; + BView * fOffscreen; + + BCursor fViewCursor; + BBitmap * fCursorBitmap; + BRect fCursorFrame; + bool fCursorVisible; + + BObjectList fStates; +}; + +#endif // REMOTE_VIEW_H