blob: 0491283f1d8db700c05a18af92b42d51e3832cb9 [file] [log] [blame]
// Copyright 2016 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/emulation/ClipboardPipe.h"
#include "android/base/memory/LazyInstance.h"
#include "android/clipboard-pipe.h"
#include <cassert>
namespace android {
namespace emulation {
static const auto emptyCallback = [](const uint8_t*, size_t) {};
// A storage for the current clipboard pipe instance data.
// Shared pointer is required as there are potential races between clipboard
// data changing from the host and guest's request to close the pipe. We need
// to keep the object alive if we've started some operation.
struct ClipboardPipeInstance {
android::base::Lock pipeLock;
ClipboardPipe::Ptr pipe;
ClipboardPipe::GuestClipboardCallback guestClipboardCallback =
emptyCallback;
};
static android::base::LazyInstance<ClipboardPipeInstance> sInstance = {};
ClipboardPipe::ClipboardPipe(void* hwPipe, Service* svc)
: AndroidPipe(hwPipe, svc) {}
void ClipboardPipe::onGuestClose() {
ClipboardPipe::Service::closePipe();
}
unsigned ClipboardPipe::onGuestPoll() const {
unsigned result = PIPE_POLL_OUT;
if (mHostClipboardHasNewData) {
result |= PIPE_POLL_IN;
}
return result;
}
int ClipboardPipe::processOperation(OperationType opType,
ReadWriteState* state,
const AndroidPipeBuffer* pipeBuffers,
int numPipeBuffers,
bool* opComplete) {
// This indicates the number of bytes needed to be read from or written
// to the pipe.
uint32_t requiredBytes = state->sizeProcessed
? state->requiredBytes
: sizeof(state->requiredBytes);
// This will point to the buffer to be read from.
const uint8_t* sourceBuffer = nullptr;
// This will point to the buffer to be written into.
uint8_t* targetBuffer = nullptr;
const AndroidPipeBuffer* pipeBuffer = pipeBuffers;
const AndroidPipeBuffer* endPipeBuffer = pipeBuffer + numPipeBuffers;
// This will contain the return value of the function (number of bytes
// read or written).
int totalBytesProcessed = 0;
// Indicates the offset within the pipe buffer that is currently being
// processed.
size_t pipeBufferOffset = 0;
*opComplete = false;
if (opType == OperationType::WriteToGuestClipboard) {
// If we're writing to the guest clipboard, then the source
// buffer is state.buffer (or state.requiredBytes, if we haven't
// sent the length of the buffer yet).
sourceBuffer =
state->sizeProcessed
? state->buffer.data()
: reinterpret_cast<uint8_t*>(&(state->requiredBytes));
} else if (opType == OperationType::ReadFromGuestClipboard) {
// If we're reading from the guest clipboard, the target buffer
// is the state's buffer (or state.requiredBytes, if we haven't
// read the length of the buffer yet).
targetBuffer =
state->sizeProcessed
? state->buffer.data()
: reinterpret_cast<uint8_t*>(&(state->requiredBytes));
}
while (pipeBuffer != endPipeBuffer) {
// Decide how many bytes need to be read/written during the current
// iteration.
int bytesToProcess = std::min(
requiredBytes - state->processedBytes,
static_cast<uint32_t>(pipeBuffer->size - pipeBufferOffset));
if (opType == OperationType::WriteToGuestClipboard) {
// const_cast is ok here since the contents of AndroidPipeBuffer
// passed into the function were intended to be changed.
targetBuffer =
const_cast<uint8_t*>(pipeBuffer->data) + pipeBufferOffset;
memcpy(targetBuffer, sourceBuffer + state->processedBytes,
bytesToProcess);
} else if (opType == OperationType::ReadFromGuestClipboard) {
sourceBuffer = static_cast<const uint8_t*>(pipeBuffer->data) +
pipeBufferOffset;
memcpy(targetBuffer + state->processedBytes, sourceBuffer,
bytesToProcess);
}
state->processedBytes += bytesToProcess;
totalBytesProcessed += bytesToProcess;
pipeBufferOffset += bytesToProcess;
if (pipeBufferOffset >= pipeBuffer->size) {
++pipeBuffer;
pipeBufferOffset = 0;
}
if (state->processedBytes == requiredBytes && !state->sizeProcessed) {
// We have either sent or received the length of clipboard data
// in bytes. Now it is time to send/receive the buffer itself.
state->sizeProcessed = true;
state->processedBytes = 0;
requiredBytes = state->requiredBytes;
if (opType == OperationType::ReadFromGuestClipboard) {
// If we're reading from guest clipboard,
// make sure the buffer on our side has enough space.
state->buffer.resize(state->requiredBytes);
// Make sure next iterations write to state's buffer.
targetBuffer = state->buffer.data();
} else if (opType == OperationType::WriteToGuestClipboard) {
// If we're writing to the guest clipboard, just
// make sure next iterations read from state's buffer.
sourceBuffer = state->buffer.data();
}
}
}
if (state->processedBytes == state->requiredBytes && state->sizeProcessed) {
*opComplete = true;
}
return totalBytesProcessed;
}
int ClipboardPipe::onGuestRecv(AndroidPipeBuffer* buffers, int numBuffers) {
int result = PIPE_ERROR_AGAIN;
if (mHostClipboardHasNewData) {
bool opComplete = false;
result = processOperation(OperationType::WriteToGuestClipboard,
&mGuestClipboardWriteState, buffers,
numBuffers, &opComplete);
if (opComplete) {
mHostClipboardHasNewData = false;
}
}
return result;
}
int ClipboardPipe::onGuestSend(const AndroidPipeBuffer* buffers,
int numBuffers) {
bool opComplete = false;
int result = processOperation(OperationType::ReadFromGuestClipboard,
&mGuestClipboardReadState, buffers,
numBuffers, &opComplete);
if (opComplete) {
sInstance->guestClipboardCallback(
mGuestClipboardReadState.buffer.data(),
mGuestClipboardReadState.processedBytes);
mGuestClipboardReadState.sizeProcessed = false;
mGuestClipboardReadState.requiredBytes = 0;
mGuestClipboardReadState.processedBytes = 0;
}
return result;
}
void registerClipboardPipeService() {
android::AndroidPipe::Service::add(new ClipboardPipe::Service());
}
void ClipboardPipe::setGuestClipboardCallback(
ClipboardPipe::GuestClipboardCallback cb) {
sInstance->guestClipboardCallback = cb ? cb : emptyCallback;
}
void ClipboardPipe::setGuestClipboardContents(const uint8_t* buf, size_t len) {
mGuestClipboardWriteState.buffer.assign(buf, buf + len);
mGuestClipboardWriteState.sizeProcessed = false;
mGuestClipboardWriteState.requiredBytes = len;
mGuestClipboardWriteState.processedBytes = 0;
mHostClipboardHasNewData = true;
if (mWakeOnRead) {
signalWake(PIPE_WAKE_READ);
}
}
////////////////////////////////////////////////////////////////////////////////
ClipboardPipe::Service::Service() : AndroidPipe::Service("clipboard") {}
AndroidPipe* ClipboardPipe::Service::create(void* hwPipe, const char* args) {
const auto pipe = new ClipboardPipe(hwPipe, this);
{
android::base::AutoLock lock(sInstance->pipeLock);
assert(!sInstance->pipe);
sInstance->pipe.reset(pipe);
}
return pipe;
}
ClipboardPipe::Ptr ClipboardPipe::Service::getPipe() {
android::base::AutoLock lock(sInstance->pipeLock);
return sInstance->pipe;
}
void ClipboardPipe::Service::closePipe() {
android::base::AutoLock lock(sInstance->pipeLock);
sInstance->pipe.reset();
}
} // namespace emulation
} // namespace android
void android_init_clipboard_pipe(void) {
android::emulation::registerClipboardPipeService();
}