Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/lib/client/ServerProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *
return Unknown;
}

// any recognized inbound traffic proves the server is alive
resetKeepAliveAlarm();
return Okay;
}

Expand Down Expand Up @@ -321,6 +323,12 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code)
return Unknown;
}

// any recognized inbound traffic proves the server is alive; reset
// the watchdog so a long bulk transfer (e.g. multi-MB clipboard
// chunks from the server) does not starve the explicit keep-alive
// echo and trigger a false "server is dead" alarm.
resetKeepAliveAlarm();

// send a reply. this is intended to work around a delay when
// running a linux server and an OS X (any BSD?) client. the
// client waits to send an ACK (if the system control flag
Expand Down
12 changes: 10 additions & 2 deletions src/lib/deskflow/ClipboardChunk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ ClipboardChunk::assemble(deskflow::IStream *stream, std::string &dataCached, Cli
}

if (mark == ChunkType::DataStart) {
s_expectedSize = QString::fromStdString(data).toULong();
bool ok = false;
s_expectedSize = QString::fromStdString(data).toULong(&ok);
if (!ok) {
LOG_ERR("invalid clipboard size string");
return Error;
}
LOG_DEBUG("start receiving clipboard data");
dataCached.clear();
return Started;
Expand All @@ -86,7 +91,10 @@ ClipboardChunk::assemble(deskflow::IStream *stream, std::string &dataCached, Cli
if (id >= kClipboardEnd) {
return Error;
} else if (s_expectedSize != dataCached.size()) {
LOG_ERR("corrupted clipboard data, expected size=%d actual size=%d", s_expectedSize, dataCached.size());
LOG_ERR(
"corrupted clipboard data, expected size=%zu actual size=%zu", static_cast<size_t>(s_expectedSize),
dataCached.size()
);
return Error;
}
return Finished;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/deskflow/IClipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ void IClipboard::unmarshall(IClipboard *clipboard, const std::string_view &data,
clipboard->empty();

// read the number of formats
const uint32_t numFormats = readUInt32(index);
if (end - index < 4) {
LOG_ERR("clipboard unmarshall: truncated header");
clipboard->close();
return;
}
const uint32_t numFormats = readUInt32(index);
index += 4;

// read each format
Expand Down
5 changes: 1 addition & 4 deletions src/lib/deskflow/StreamChunker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void StreamChunker::sendClipboard(
size_t sentLength = 0;
size_t chunkSize = g_chunkSize;

while (true) {
while (sentLength < size) {
// make sure we don't read too much from the mock data.
if (sentLength + chunkSize > size) {
chunkSize = size - sentLength;
Expand All @@ -39,9 +39,6 @@ void StreamChunker::sendClipboard(
events->addEvent(Event(EventTypes::ClipboardSending, eventTarget, dataChunk));

sentLength += chunkSize;
if (sentLength == size) {
break;
}
}

// send last message
Expand Down
9 changes: 6 additions & 3 deletions src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ MSWindowsClipboardAnyTextConverter::fromIClipboard(const std::string &data) cons

std::string MSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const
{
// get datator
// get data pointer
const char *src = (const char *)GlobalLock(data);
uint32_t srcSize = (uint32_t)GlobalSize(data);
if (src == nullptr || srcSize <= 1) {
if (src != nullptr) {
GlobalUnlock(data);
}
return std::string();
}

Expand Down Expand Up @@ -97,7 +100,7 @@ std::string MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(const std:
uint32_t numNewlines = 0;
uint32_t n = (uint32_t)src.size();
for (const char *scan = src.c_str(); n > 0; ++scan, --n) {
if (scan[0] == '\r' && scan[1] == '\n') {
if (scan[0] == '\r' && n > 1 && scan[1] == '\n') {
++numNewlines;
}
}
Expand All @@ -112,7 +115,7 @@ std::string MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(const std:
// copy string, converting newlines
n = (uint32_t)src.size();
for (const char *scan = src.c_str(); n > 0; ++scan, --n) {
if (scan[0] != '\r' || scan[1] != '\n') {
if (scan[0] != '\r' || n <= 1 || scan[1] != '\n') {
dst += scan[0];
}
}
Expand Down
66 changes: 52 additions & 14 deletions src/lib/platform/MSWindowsClipboardBitmapConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,18 @@ MSWindowsClipboardBitmapConverter::fromIClipboard(const std::string &data) const

std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const
{
// get datator
// get data pointer
LPVOID src = GlobalLock(data);
if (src == nullptr) {
return std::string();
}
uint32_t srcSize = (uint32_t)GlobalSize(data);

if (srcSize < sizeof(BITMAPINFOHEADER)) {
GlobalUnlock(data);
return std::string();
}

// check image type
const BITMAPINFO *bitmap = static_cast<const BITMAPINFO *>(src);
LOG(
Expand All @@ -66,12 +71,26 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const
return image;
}

// validate dimensions
LONG w = bitmap->bmiHeader.biWidth;
LONG h = bitmap->bmiHeader.biHeight;
if (w <= 0 || h == 0) {
GlobalUnlock(data);
return std::string();
}
LONG absH = (h > 0) ? h : -h;

// check for integer overflow in pixel data size (4 bytes per pixel)
if (w > 32767 || absH > 32767 || static_cast<uint64_t>(w) * absH > 0x40000000ULL) {
LOG_WARN("bitmap too large: %dx%d", w, absH);
GlobalUnlock(data);
return std::string();
}

// create a destination DIB section
LOG_INFO("convert image from: depth=%d comp=%d", bitmap->bmiHeader.biBitCount, bitmap->bmiHeader.biCompression);
void *raw;
BITMAPINFOHEADER info;
LONG w = bitmap->bmiHeader.biWidth;
LONG h = bitmap->bmiHeader.biHeight;
info.biSize = sizeof(BITMAPINFOHEADER);
info.biWidth = w;
info.biHeight = h;
Expand All @@ -84,33 +103,52 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const
info.biClrUsed = 0;
info.biClrImportant = 0;
HDC dc = GetDC(nullptr);
if (dc == nullptr) {
GlobalUnlock(data);
return std::string();
}
HBITMAP dst = CreateDIBSection(dc, (BITMAPINFO *)&info, DIB_RGB_COLORS, &raw, nullptr, 0);
if (dst == nullptr) {
ReleaseDC(nullptr, dc);
GlobalUnlock(data);
return std::string();
}

// find the start of the pixel data
const char *srcBits = (const char *)bitmap + bitmap->bmiHeader.biSize;
if (bitmap->bmiHeader.biBitCount >= 16) {
if (bitmap->bmiHeader.biCompression == BI_BITFIELDS &&
(bitmap->bmiHeader.biBitCount == 16 || bitmap->bmiHeader.biBitCount == 32)) {
srcBits += 3 * sizeof(DWORD);
// Only BITMAPINFOHEADER (40 bytes) has color masks/table after the header.
// BITMAPV4HEADER (108 bytes) and BITMAPV5HEADER (124 bytes) include
// masks and color space info within the header itself.
if (bitmap->bmiHeader.biSize == sizeof(BITMAPINFOHEADER)) {
if (bitmap->bmiHeader.biBitCount >= 16) {
if (bitmap->bmiHeader.biCompression == BI_BITFIELDS &&
(bitmap->bmiHeader.biBitCount == 16 || bitmap->bmiHeader.biBitCount == 32)) {
srcBits += 3 * sizeof(DWORD);
}
} else if (bitmap->bmiHeader.biClrUsed != 0) {
srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD);
} else if (bitmap->bmiHeader.biBitCount > 0 && bitmap->bmiHeader.biBitCount <= 8) {
srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD);
}
} else if (bitmap->bmiHeader.biClrUsed != 0) {
srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD);
} else {
// http://msdn.microsoft.com/en-us/library/ke55d167(VS.80).aspx
srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD);
}

// copy source image to destination image
HDC dstDC = CreateCompatibleDC(dc);
if (dstDC == nullptr) {
DeleteObject(dst);
ReleaseDC(nullptr, dc);
GlobalUnlock(data);
return std::string();
}
HGDIOBJ oldBitmap = SelectObject(dstDC, dst);
SetDIBitsToDevice(dstDC, 0, 0, w, h, 0, 0, 0, h, srcBits, bitmap, DIB_RGB_COLORS);
SetDIBitsToDevice(dstDC, 0, 0, w, absH, 0, 0, 0, absH, srcBits, bitmap, DIB_RGB_COLORS);
SelectObject(dstDC, oldBitmap);
DeleteDC(dstDC);
GdiFlush();

// extract data
std::string image((const char *)&info, info.biSize);
image.append((const char *)raw, 4 * w * h);
image.append((const char *)raw, 4LL * w * absH);

// clean up GDI
DeleteObject(dst);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/platform/MSWindowsClipboardHTMLConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ std::string MSWindowsClipboardHTMLConverter::doToIClipboard(const std::string &d
// convert args to integers
int32_t start = (int32_t)atoi(startArg.c_str());
int32_t end = (int32_t)atoi(endArg.c_str());
if (start <= 0 || end <= 0 || start >= end) {
if (start <= 0 || end <= 0 || start >= end || static_cast<size_t>(end) > data.size()) {
return std::string();
}

Expand Down
26 changes: 20 additions & 6 deletions src/lib/platform/MSWindowsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,9 @@ void MSWindowsScreen::checkClipboards()
if (m_ownClipboard && !MSWindowsClipboard::isOwnedByDeskflow()) {
LOG_DEBUG("clipboard changed: lost ownership and no notification received");
m_ownClipboard = false;
// Windows has a single OS clipboard, so we only grab kClipboardClipboard.
// See OSXScreen::checkClipboards for the reasoning.
sendClipboardEvent(EventTypes::ClipboardGrabbed, kClipboardClipboard);
sendClipboardEvent(EventTypes::ClipboardGrabbed, kClipboardSelection);
}
}

Expand Down Expand Up @@ -669,13 +670,25 @@ bool MSWindowsScreen::isAnyMouseButtonDown(uint32_t &buttonID) const
{
static const char *buttonToName[] = {"<invalid>", "Left Button", "Middle Button",
"Right Button", "X Button 1", "X Button 2"};
// index by ButtonID (kButtonNone, kButtonLeft, kButtonMiddle, kButtonRight, X1, X2)
static const int vkCodes[] = {0, VK_LBUTTON, VK_MBUTTON, VK_RBUTTON, VK_XBUTTON1, VK_XBUTTON2};

for (uint32_t i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) {
if (m_buttons[i]) {
buttonID = i;
LOG_DEBUG("locked by \"%s\"", buttonToName[i]);
return true;
if (!m_buttons[i]) {
continue;
}
// verify against actual hardware state: a button-up event from the
// low-level hook can be lost (e.g. when the hook is bypassed during
// heavy clipboard transfer), leaving the cached state stuck "down"
// and the cursor permanently locked to the screen.
if (!(GetAsyncKeyState(vkCodes[i]) & 0x8000)) {
LOG_WARN("clearing stuck mouse button state: \"%s\"", buttonToName[i]);
m_buttons[i] = false;
continue;
}
buttonID = i;
LOG_DEBUG("locked by \"%s\"", buttonToName[i]);
return true;
}

return false;
Expand Down Expand Up @@ -1342,8 +1355,9 @@ void MSWindowsScreen::onClipboardChange()
if (m_ownClipboard) {
LOG_DEBUG("clipboard changed: lost ownership");
m_ownClipboard = false;
// Windows has a single OS clipboard, so we only grab kClipboardClipboard.
// See OSXScreen::checkClipboards for the reasoning.
sendClipboardEvent(EventTypes::ClipboardGrabbed, kClipboardClipboard);
sendClipboardEvent(EventTypes::ClipboardGrabbed, kClipboardSelection);
}
} else if (!m_ownClipboard) {
LOG_DEBUG("clipboard changed: %s owned", kAppId);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/platform/MSWindowsScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ class MSWindowsScreen : public PlatformScreen
HotKeyToIDMap m_hotKeyToIDMap;

// map of button state
bool m_buttons[NumButtonIDs];
mutable bool m_buttons[NumButtonIDs];

// m_hasMouse is true if there's a mouse attached to the system or
// mouse keys is simulating one. we track this so we can force the
Expand Down
14 changes: 14 additions & 0 deletions src/lib/platform/OSXClipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,20 @@ bool OSXClipboard::has(Format format) const
PasteboardItemID item;
PasteboardGetItemIdentifier(m_pboard, (CFIndex)1, &item);

// For bitmap format, only report available if the pasteboard actually
// contains native image data (TIFF or PNG). Without this check, macOS
// may claim com.microsoft.bmp is available via UTI auto-conversion
// for non-image pasteboard content (e.g. file references), but the
// actual conversion can hang and block the event loop, causing the
// client to disconnect due to keep-alive timeout.
if (format == IClipboard::Format::Bitmap) {
PasteboardFlavorFlags flags;
if (PasteboardGetItemFlavorFlags(m_pboard, item, CFSTR("public.tiff"), &flags) != noErr &&
PasteboardGetItemFlavorFlags(m_pboard, item, CFSTR("public.png"), &flags) != noErr) {
return false;
}
}

for (ConverterList::const_iterator index = m_converters.begin(); index != m_converters.end(); ++index) {
IOSXClipboardConverter *converter = *index;
if (converter->getFormat() == format) {
Expand Down
Loading
Loading