[idd] helper: capture file clipboard under lock

This commit is contained in:
Geoffrey McRae
2026-08-14 20:05:44 +10:00
parent 352c3c42b6
commit e40dc19c7e
5 changed files with 668 additions and 157 deletions

View File

@@ -48,6 +48,7 @@ namespace
private: private:
HANDLE m_previousToken = nullptr; HANDLE m_previousToken = nullptr;
bool m_active = false; bool m_active = false;
DWORD m_error = ERROR_SUCCESS;
void ClosePreviousToken() void ClosePreviousToken()
{ {
@@ -76,6 +77,7 @@ namespace
if (!m_previousToken && SetThreadToken(nullptr, nullptr)) if (!m_previousToken && SetThreadToken(nullptr, nullptr))
{ {
m_active = false; m_active = false;
m_error = error;
SetLastError(error); SetLastError(error);
return false; return false;
} }
@@ -97,7 +99,10 @@ namespace
{ {
const DWORD error = GetLastError(); const DWORD error = GetLastError();
if (error != ERROR_NO_TOKEN) if (error != ERROR_NO_TOKEN)
{
m_error = error;
return; return;
}
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
} }
@@ -107,6 +112,7 @@ namespace
{ {
const DWORD error = GetLastError(); const DWORD error = GetLastError();
ClosePreviousToken(); ClosePreviousToken();
m_error = error;
SetLastError(error); SetLastError(error);
} }
} }
@@ -127,6 +133,11 @@ namespace
return m_active; return m_active;
} }
DWORD Error() const
{
return m_error;
}
bool Finish() bool Finish()
{ {
const DWORD error = GetLastError(); const DWORD error = GetLastError();
@@ -150,13 +161,15 @@ namespace
} }
bool CaptureUserToken(HANDLE& token, bool CaptureUserToken(HANDLE& token,
KVMFRClipboardFileError& error) KVMFRClipboardFileError& error, DWORD& winError)
{ {
token = nullptr; token = nullptr;
winError = ERROR_SUCCESS;
DWORD sessionId = 0; DWORD sessionId = 0;
if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId)) if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId))
{ {
error = TokenError(GetLastError()); winError = GetLastError();
error = TokenError(winError);
return false; return false;
} }
@@ -164,7 +177,8 @@ namespace
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE,
&processToken)) &processToken))
{ {
error = TokenError(GetLastError()); winError = GetLastError();
error = TokenError(winError);
return false; return false;
} }
@@ -177,7 +191,7 @@ namespace
CloseHandle(processToken); CloseHandle(processToken);
if (!brokerDuplicated) if (!brokerDuplicated)
{ {
SetLastError(brokerError); winError = brokerError;
error = TokenError(brokerError); error = TokenError(brokerError);
return false; return false;
} }
@@ -185,9 +199,8 @@ namespace
LUID privilege = {}; LUID privilege = {};
if (!LookupPrivilegeValueW(nullptr, SE_TCB_NAME, &privilege)) if (!LookupPrivilegeValueW(nullptr, SE_TCB_NAME, &privilege))
{ {
const DWORD winError = GetLastError(); winError = GetLastError();
CloseHandle(brokerToken); CloseHandle(brokerToken);
SetLastError(winError);
error = TokenError(winError); error = TokenError(winError);
return false; return false;
} }
@@ -197,22 +210,23 @@ namespace
privileges.Privileges[0].Luid = privilege; privileges.Privileges[0].Luid = privilege;
privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
if (!AdjustTokenPrivileges(brokerToken, FALSE, &privileges, 0, nullptr, const bool adjusted = AdjustTokenPrivileges(brokerToken, FALSE,
nullptr) || GetLastError() != ERROR_SUCCESS) &privileges, 0, nullptr, nullptr) != FALSE;
const DWORD adjustError = GetLastError();
if (!adjusted || adjustError != ERROR_SUCCESS)
{ {
const DWORD winError = GetLastError();
CloseHandle(brokerToken); CloseHandle(brokerToken);
SetLastError(winError ? winError : ERROR_ACCESS_DENIED); winError = adjustError ? adjustError : ERROR_ACCESS_DENIED;
error = TokenError(winError ? winError : ERROR_ACCESS_DENIED); error = TokenError(winError);
return false; return false;
} }
CThreadImpersonation brokerImpersonation(brokerToken); CThreadImpersonation brokerImpersonation(brokerToken);
if (!brokerImpersonation.Active()) if (!brokerImpersonation.Active())
{ {
const DWORD winError = GetLastError(); const DWORD brokerImpersonationError = brokerImpersonation.Error();
CloseHandle(brokerToken); CloseHandle(brokerToken);
SetLastError(winError); winError = brokerImpersonationError;
error = TokenError(winError); error = TokenError(winError);
return false; return false;
} }
@@ -222,11 +236,11 @@ namespace
const DWORD queryError = queried ? ERROR_SUCCESS : GetLastError(); const DWORD queryError = queried ? ERROR_SUCCESS : GetLastError();
if (!brokerImpersonation.Finish()) if (!brokerImpersonation.Finish())
{ {
const DWORD winError = GetLastError(); const DWORD restoreError = brokerImpersonation.Error();
if (sourceToken) if (sourceToken)
CloseHandle(sourceToken); CloseHandle(sourceToken);
CloseHandle(brokerToken); CloseHandle(brokerToken);
SetLastError(winError); winError = restoreError;
error = TokenError(winError); error = TokenError(winError);
return false; return false;
} }
@@ -235,8 +249,8 @@ namespace
{ {
if (sourceToken) if (sourceToken)
CloseHandle(sourceToken); CloseHandle(sourceToken);
SetLastError(queryError ? queryError : ERROR_ACCESS_DENIED); winError = queryError ? queryError : ERROR_ACCESS_DENIED;
error = TokenError(queryError ? queryError : ERROR_ACCESS_DENIED); error = TokenError(winError);
return false; return false;
} }
@@ -247,7 +261,7 @@ namespace
CloseHandle(sourceToken); CloseHandle(sourceToken);
if (!duplicated) if (!duplicated)
{ {
SetLastError(duplicateError); winError = duplicateError;
error = TokenError(duplicateError); error = TokenError(duplicateError);
return false; return false;
} }
@@ -1148,6 +1162,61 @@ namespace
}; };
} }
class CClipboardUserImpersonation::Impl
{
public:
HANDLE token;
CThreadImpersonation impersonation;
explicit Impl(HANDLE token) :
token(token), impersonation(token)
{
}
~Impl()
{
impersonation.Finish();
CloseHandle(token);
}
};
CClipboardUserImpersonation::CClipboardUserImpersonation(
KVMFRClipboardFileError& error)
{
HANDLE token = nullptr;
if (!CaptureUserToken(token, error, m_error))
return;
try
{
m_impl = std::make_unique<Impl>(token);
}
catch (const std::bad_alloc&)
{
CloseHandle(token);
m_error = ERROR_OUTOFMEMORY;
error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY;
return;
}
if (!m_impl->impersonation.Active())
{
m_error = m_impl->impersonation.Error();
m_impl.reset();
error = TokenError(m_error);
}
}
CClipboardUserImpersonation::~CClipboardUserImpersonation() = default;
bool CClipboardUserImpersonation::Active() const
{
return m_impl && m_impl->impersonation.Active();
}
DWORD CClipboardUserImpersonation::Error() const
{
return m_error;
}
CLocalClipboardFiles::CLocalClipboardFiles(HANDLE userToken) : CLocalClipboardFiles::CLocalClipboardFiles(HANDLE userToken) :
m_userToken(userToken) m_userToken(userToken)
{ {
@@ -1173,7 +1242,8 @@ std::shared_ptr<CLocalClipboardFiles> CLocalClipboardFiles::Capture(
std::shared_ptr<CLocalClipboardFiles> dataset; std::shared_ptr<CLocalClipboardFiles> dataset;
try try
{ {
if (!CaptureUserToken(userToken, error)) DWORD tokenError = ERROR_SUCCESS;
if (!CaptureUserToken(userToken, error, tokenError))
return nullptr; return nullptr;
CLocalClipboardFiles * raw = new CLocalClipboardFiles(userToken); CLocalClipboardFiles * raw = new CLocalClipboardFiles(userToken);
userToken = nullptr; userToken = nullptr;
@@ -1213,7 +1283,7 @@ std::shared_ptr<CLocalClipboardFiles> CLocalClipboardFiles::Capture(
CThreadImpersonation impersonation(dataset->m_userToken); CThreadImpersonation impersonation(dataset->m_userToken);
if (!impersonation.Active()) if (!impersonation.Active())
{ {
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return nullptr; return nullptr;
} }
for (const std::wstring& root : roots) for (const std::wstring& root : roots)
@@ -1221,7 +1291,7 @@ std::shared_ptr<CLocalClipboardFiles> CLocalClipboardFiles::Capture(
return nullptr; return nullptr;
if (!impersonation.Finish()) if (!impersonation.Finish())
{ {
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return nullptr; return nullptr;
} }
} }
@@ -1512,14 +1582,14 @@ bool CLocalClipboardFiles::List(uint64_t node, std::vector<uint8_t>& data,
CThreadImpersonation impersonation(m_userToken); CThreadImpersonation impersonation(m_userToken);
if (!impersonation.Active()) if (!impersonation.Active())
{ {
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return false; return false;
} }
if (!RefreshRoots(error)) if (!RefreshRoots(error))
return false; return false;
if (!impersonation.Finish()) if (!impersonation.Finish())
{ {
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return false; return false;
} }
m_published = true; m_published = true;
@@ -1542,14 +1612,14 @@ bool CLocalClipboardFiles::List(uint64_t node, std::vector<uint8_t>& data,
CThreadImpersonation impersonation(m_userToken); CThreadImpersonation impersonation(m_userToken);
if (!impersonation.Active()) if (!impersonation.Active())
{ {
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return false; return false;
} }
if (!LoadChildren(index, error)) if (!LoadChildren(index, error))
return false; return false;
if (!impersonation.Finish()) if (!impersonation.Finish())
{ {
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return false; return false;
} }
children = &m_nodes[index].children; children = &m_nodes[index].children;
@@ -1653,7 +1723,7 @@ bool CLocalClipboardFiles::Read(uint64_t node, uint64_t offset,
CThreadImpersonation impersonation(m_userToken); CThreadImpersonation impersonation(m_userToken);
if (!impersonation.Active()) if (!impersonation.Active())
{ {
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return false; return false;
} }
@@ -1758,7 +1828,7 @@ bool CLocalClipboardFiles::Read(uint64_t node, uint64_t offset,
if (!impersonation.Finish()) if (!impersonation.Finish())
{ {
data.clear(); data.clear();
error = TokenError(GetLastError()); error = TokenError(impersonation.Error());
return false; return false;
} }
return true; return true;

View File

@@ -45,6 +45,25 @@ struct ClipboardRemoteFileEntry
std::wstring name; std::wstring name;
}; };
class CClipboardUserImpersonation final
{
private:
class Impl;
std::unique_ptr<Impl> m_impl;
DWORD m_error = ERROR_SUCCESS;
public:
explicit CClipboardUserImpersonation(KVMFRClipboardFileError& error);
~CClipboardUserImpersonation();
CClipboardUserImpersonation(const CClipboardUserImpersonation&) = delete;
CClipboardUserImpersonation& operator=(
const CClipboardUserImpersonation&) = delete;
bool Active() const;
DWORD Error() const;
};
class CLocalClipboardFiles final class CLocalClipboardFiles final
{ {
public: public:

View File

@@ -23,6 +23,7 @@
#include <CDebug.h> #include <CDebug.h>
#include <bcrypt.h> #include <bcrypt.h>
#include <Ole2.h> #include <Ole2.h>
#include <ShlObj.h>
#include <algorithm> #include <algorithm>
#include <condition_variable> #include <condition_variable>
@@ -92,6 +93,140 @@ namespace
{ if (acquired) ReleaseStgMedium(&medium); } { if (acquired) ReleaseStgMedium(&medium); }
}; };
template<typename T>
struct ClipboardComScope
{
T * object = nullptr;
~ClipboardComScope() { if (object) object->Release(); }
};
struct ClipboardTaskStringScope
{
PWSTR value = nullptr;
~ClipboardTaskStringScope() { CoTaskMemFree(value); }
};
bool ClipboardDataObjectHasFileFormats(IDataObject * object,
DWORD sequence, HRESULT& enumError)
{
enumError = E_INVALIDARG;
if (!object)
{
DEBUG_ERROR_HR(E_POINTER,
"Failed to inspect local clipboard file formats: "
"stage=IDataObject sequence=%lu",
static_cast<unsigned long>(sequence));
return false;
}
ClipboardComScope<IEnumFORMATETC> formats;
enumError = object->EnumFormatEtc(DATADIR_GET, &formats.object);
if (FAILED(enumError) || !formats.object)
{
if (SUCCEEDED(enumError))
enumError = E_UNEXPECTED;
DEBUG_ERROR_HR(enumError,
"Failed to inspect local clipboard file formats: "
"stage=IDataObject::EnumFormatEtc sequence=%lu",
static_cast<unsigned long>(sequence));
return false;
}
const UINT shellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST);
if (!shellIDList)
{
const DWORD formatError = GetLastError();
DEBUG_ERROR_HR(formatError ? HRESULT_FROM_WIN32(formatError) :
E_UNEXPECTED,
"Failed to inspect local clipboard file formats: "
"stage=RegisterClipboardFormatW(CFSTR_SHELLIDLIST) sequence=%lu",
static_cast<unsigned long>(sequence));
}
const UINT fileDescriptor =
RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW);
if (!fileDescriptor)
{
const DWORD formatError = GetLastError();
DEBUG_ERROR_HR(formatError ? HRESULT_FROM_WIN32(formatError) :
E_UNEXPECTED,
"Failed to inspect local clipboard file formats: "
"stage=RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW) sequence=%lu",
static_cast<unsigned long>(sequence));
}
for (;;)
{
FORMATETC format = {};
ULONG fetched = 0;
enumError = formats.object->Next(1, &format, &fetched);
const bool files = fetched == 1U &&
(format.cfFormat == CF_HDROP ||
(shellIDList && format.cfFormat == shellIDList) ||
(fileDescriptor && format.cfFormat == fileDescriptor));
CoTaskMemFree(format.ptd);
if (files)
{
enumError = S_OK;
return true;
}
if (enumError == S_FALSE)
{
enumError = S_OK;
return false;
}
if (FAILED(enumError))
{
DEBUG_ERROR_HR(enumError,
"Failed to inspect local clipboard file formats: "
"stage=IEnumFORMATETC::Next sequence=%lu",
static_cast<unsigned long>(sequence));
return false;
}
if (!fetched)
{
enumError = E_UNEXPECTED;
DEBUG_ERROR_HR(enumError,
"Failed to inspect local clipboard file formats: "
"stage=IEnumFORMATETC::Next sequence=%lu fetched=0",
static_cast<unsigned long>(sequence));
return false;
}
}
}
HRESULT ClipboardFileHRESULT(KVMFRClipboardFileError error)
{
switch (error)
{
case KVMFR_CLIPBOARD_FILE_ERROR_NONE:
return S_OK;
case KVMFR_CLIPBOARD_FILE_ERROR_NOT_FOUND:
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
case KVMFR_CLIPBOARD_FILE_ERROR_ACCESS:
return E_ACCESSDENIED;
case KVMFR_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY:
case KVMFR_CLIPBOARD_FILE_ERROR_IS_DIRECTORY:
return HRESULT_FROM_WIN32(ERROR_DIRECTORY);
case KVMFR_CLIPBOARD_FILE_ERROR_IO:
return HRESULT_FROM_WIN32(ERROR_READ_FAULT);
case KVMFR_CLIPBOARD_FILE_ERROR_INVALID:
return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
case KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY:
return E_OUTOFMEMORY;
case KVMFR_CLIPBOARD_FILE_ERROR_NO_SPACE:
return HRESULT_FROM_WIN32(ERROR_DISK_FULL);
case KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED:
return HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED);
case KVMFR_CLIPBOARD_FILE_ERROR_CANCELLED:
return HRESULT_FROM_WIN32(ERROR_OPERATION_ABORTED);
case KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED:
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
case KVMFR_CLIPBOARD_FILE_ERROR_STALE:
return HRESULT_FROM_WIN32(ERROR_RETRY);
default:
return E_UNEXPECTED;
}
}
struct ClipboardOrigin struct ClipboardOrigin
{ {
uint32_t magic; uint32_t magic;
@@ -2036,11 +2171,10 @@ void CClipboardManager::HandleFileAcquire(
if (error) if (error)
DEBUG_WARN("Local clipboard file acquire failed: dataset=%llu " DEBUG_WARN("Local clipboard file acquire failed: dataset=%llu "
"acquisition=%llu fileError=%u winError=%lu", "acquisition=%llu fileError=%u",
static_cast<unsigned long long>(record.clipboardGeneration), static_cast<unsigned long long>(record.clipboardGeneration),
static_cast<unsigned long long>(record.transfer), static_cast<unsigned long long>(record.transfer),
static_cast<unsigned int>(error), static_cast<unsigned int>(error));
static_cast<unsigned long>(ERROR_SUCCESS));
else else
DEBUG_INFO("Local clipboard file acquire accepted: dataset=%llu " DEBUG_INFO("Local clipboard file acquire accepted: dataset=%llu "
"acquisition=%llu", "acquisition=%llu",
@@ -2141,11 +2275,10 @@ void CClipboardManager::HandleFileRequest(
KVMFR_CLIPBOARD_FILE_ERROR_STALE; KVMFR_CLIPBOARD_FILE_ERROR_STALE;
if (initialList) if (initialList)
DEBUG_WARN("Initial local clipboard file LIST rejected: " DEBUG_WARN("Initial local clipboard file LIST rejected: "
"dataset=%llu request=%llu fileError=%u winError=%lu", "dataset=%llu request=%llu fileError=%u",
static_cast<unsigned long long>(record.clipboardGeneration), static_cast<unsigned long long>(record.clipboardGeneration),
static_cast<unsigned long long>(record.transfer), static_cast<unsigned long long>(record.transfer),
static_cast<unsigned int>(failure), static_cast<unsigned int>(failure));
static_cast<unsigned long>(ERROR_SUCCESS));
QueueFileCancel(record, failure); QueueFileCancel(record, failure);
return; return;
} }
@@ -2158,17 +2291,16 @@ void CClipboardManager::HandleFileRequest(
catch (const std::bad_alloc&) catch (const std::bad_alloc&)
{ {
if (initialList) if (initialList)
DEBUG_WARN("Initial local clipboard file LIST allocation failed: " DEBUG_ERROR_HR(E_OUTOFMEMORY,
"dataset=%llu request=%llu fileError=%u winError=%lu", "Initial local clipboard file LIST allocation failed: "
"dataset=%llu request=%llu fileError=%u",
static_cast<unsigned long long>(record.clipboardGeneration), static_cast<unsigned long long>(record.clipboardGeneration),
static_cast<unsigned long long>(record.transfer), static_cast<unsigned long long>(record.transfer),
static_cast<unsigned int>(KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY), static_cast<unsigned int>(KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY));
static_cast<unsigned long>(ERROR_OUTOFMEMORY));
QueueFileCancel(record, KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY); QueueFileCancel(record, KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY);
return; return;
} }
bool success = false; bool success = false;
SetLastError(ERROR_SUCCESS);
try try
{ {
if (record.token == KVMFR_CLIPBOARD_FILE_OP_LIST) if (record.token == KVMFR_CLIPBOARD_FILE_OP_LIST)
@@ -2180,18 +2312,16 @@ void CClipboardManager::HandleFileRequest(
catch (const std::bad_alloc&) catch (const std::bad_alloc&)
{ {
error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY;
SetLastError(ERROR_OUTOFMEMORY);
} }
const DWORD winError = success ? ERROR_SUCCESS : GetLastError();
if (!success) if (!success)
{ {
if (initialList) if (initialList)
DEBUG_WARN("Initial local clipboard file LIST failed: dataset=%llu " DEBUG_ERROR_HR(ClipboardFileHRESULT(error),
"request=%llu fileError=%u winError=%lu", "Initial local clipboard file LIST failed: dataset=%llu "
"request=%llu fileError=%u",
static_cast<unsigned long long>(record.clipboardGeneration), static_cast<unsigned long long>(record.clipboardGeneration),
static_cast<unsigned long long>(record.transfer), static_cast<unsigned long long>(record.transfer),
static_cast<unsigned int>(error), static_cast<unsigned int>(error));
static_cast<unsigned long>(winError));
QueueFileCancel(record, error); QueueFileCancel(record, error);
return; return;
} }
@@ -3151,12 +3281,29 @@ void CClipboardManager::HandleFileDataObject(UIWork& work)
m_ownedSequence = GetClipboardSequenceNumber(); m_ownedSequence = GetClipboardSequenceNumber();
} }
bool CClipboardManager::OpenClipboardRetry() const bool CClipboardManager::OpenClipboardRetry(DWORD * error,
const char * stage, bool useWindow) const
{ {
for (unsigned int attempt = 0; attempt != 8; ++attempt) for (unsigned int attempt = 0; attempt != 8; ++attempt)
{ {
if (OpenClipboard(m_hwnd)) if (OpenClipboard(useWindow ? m_hwnd : nullptr))
{
if (error)
*error = ERROR_SUCCESS;
return true; return true;
}
const DWORD openError = GetLastError();
DEBUG_ERROR_HR(openError ? HRESULT_FROM_WIN32(openError) :
CLIPBRD_E_CANT_OPEN,
"OpenClipboard failed: stage=%s attempt=%u",
stage ? stage : "unspecified", attempt + 1U);
if (attempt + 1U == 8U)
{
if (error)
*error = openError;
SetLastError(openError);
return false;
}
Sleep(5U << (std::min)(attempt, 5U)); Sleep(5U << (std::min)(attempt, 5U));
} }
return false; return false;
@@ -3216,64 +3363,128 @@ uint32_t CClipboardManager::EnumerateFormats() const
} }
std::shared_ptr<CLocalClipboardFiles> std::shared_ptr<CLocalClipboardFiles>
CClipboardManager::CaptureClipboardFiles(DWORD sequence, bool& viaOLE, CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount,
bool& openedOLE, KVMFRClipboardFileError& error, DWORD& winError, bool& viaOLE, const char *& retryStage,
HRESULT& oleError) KVMFRClipboardFileError& error, HRESULT& oleError)
{ {
viaOLE = false; viaOLE = false;
openedOLE = false; retryStage = nullptr;
error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID;
winError = ERROR_SUCCESS;
oleError = S_FALSE; oleError = S_FALSE;
const bool win32Files = IsClipboardFormatAvailable(CF_HDROP) != FALSE; const DWORD initialSequence = GetClipboardSequenceNumber();
if (win32Files) if (initialSequence != sequence)
{ {
if (GetClipboardSequenceNumber() != sequence) DEBUG_ERROR_HR(HRESULT_FROM_WIN32(ERROR_RETRY),
{ "Failed to capture local clipboard files: "
winError = ERROR_RETRY; "stage=sequence-before-capture expected=%lu actual=%lu",
static_cast<unsigned long>(sequence),
static_cast<unsigned long>(initialSequence));
return nullptr; return nullptr;
} }
if (OpenClipboardRetry())
CClipboardUserImpersonation user(error);
if (!user.Active())
{ {
const DWORD userError = user.Error();
oleError = userError ? HRESULT_FROM_WIN32(userError) :
ClipboardFileHRESULT(error);
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: "
"stage=impersonate-interactive-user sequence=%lu rawFormats=%d "
"fileError=%u", static_cast<unsigned long>(sequence),
rawFormatCount, static_cast<unsigned int>(error));
return nullptr;
}
// Acquire the clipboard before probing CF_HDROP. Explorer can still hold
// the clipboard when WM_CLIPBOARDUPDATE is delivered, in which case an
// unlocked IsClipboardFormatAvailable probe can observe no formats.
DWORD openError = ERROR_SUCCESS;
if (!OpenClipboardRetry(&openError, "CaptureClipboardFiles(CF_HDROP)",
false))
{
oleError = CLIPBRD_E_CANT_OPEN;
retryStage = "OpenClipboard";
return nullptr;
}
std::shared_ptr<CLocalClipboardFiles> files; std::shared_ptr<CLocalClipboardFiles> files;
bool win32Candidate = false;
bool sequenceChanged = false;
int openedRawFormatCount = 0;
if (GetClipboardSequenceNumber() == sequence) if (GetClipboardSequenceNumber() == sequence)
{
openedRawFormatCount = CountClipboardFormats();
win32Candidate = IsClipboardFormatAvailable(CF_HDROP) != FALSE;
if (win32Candidate)
{ {
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
HDROP drop = static_cast<HDROP>(GetClipboardData(CF_HDROP)); HDROP drop = static_cast<HDROP>(GetClipboardData(CF_HDROP));
if (drop) if (drop)
{ {
SetLastError(ERROR_SUCCESS);
files = CLocalClipboardFiles::Capture(drop, error); files = CLocalClipboardFiles::Capture(drop, error);
if (!files) if (!files)
winError = GetLastError(); {
DEBUG_ERROR_HR(ClipboardFileHRESULT(error),
"Failed to capture local clipboard files: "
"stage=CLocalClipboardFiles::Capture(CF_HDROP/win32) "
"sequence=%lu rawFormats=%d openedFormats=%d fileError=%u",
static_cast<unsigned long>(sequence), rawFormatCount,
openedRawFormatCount, static_cast<unsigned int>(error));
}
} }
else else
{ {
winError = GetLastError(); DWORD dataError = GetLastError();
if (!winError) if (!dataError)
winError = ERROR_INVALID_DATA; dataError = ERROR_INVALID_DATA;
DEBUG_ERROR_HR(HRESULT_FROM_WIN32(dataError),
"Failed to capture local clipboard files: "
"stage=GetClipboardData(CF_HDROP) sequence=%lu rawFormats=%d "
"openedFormats=%d",
static_cast<unsigned long>(sequence), rawFormatCount,
openedRawFormatCount);
} }
} }
else else
winError = ERROR_RETRY; DEBUG_TRACE("Local clipboard does not expose Win32 CF_HDROP: "
CloseClipboard(); "sequence=%lu rawFormats=%d openedFormats=%d",
if (files) static_cast<unsigned long>(sequence), rawFormatCount,
return files; openedRawFormatCount);
} }
else else
{ {
winError = GetLastError(); sequenceChanged = true;
error = winError == ERROR_ACCESS_DENIED ? DEBUG_ERROR_HR(HRESULT_FROM_WIN32(ERROR_RETRY),
KVMFR_CLIPBOARD_FILE_ERROR_ACCESS : "Failed to capture local clipboard files: "
KVMFR_CLIPBOARD_FILE_ERROR_IO; "stage=sequence-after-OpenClipboard expected=%lu actual=%lu",
} static_cast<unsigned long>(sequence),
static_cast<unsigned long>(GetClipboardSequenceNumber()));
} }
if (GetClipboardSequenceNumber() != sequence) if (!CloseClipboard())
{ {
if (!winError) const DWORD closeError = GetLastError();
winError = ERROR_RETRY; DEBUG_ERROR_HR(closeError ? HRESULT_FROM_WIN32(closeError) :
CLIPBRD_E_CANT_CLOSE,
"Failed to capture local clipboard files: stage=CloseClipboard "
"sequence=%lu rawFormats=%d",
static_cast<unsigned long>(sequence), rawFormatCount);
}
if (files)
return files;
if (sequenceChanged)
return nullptr;
const DWORD oleSequence = GetClipboardSequenceNumber();
if (oleSequence != sequence)
{
DEBUG_ERROR_HR(HRESULT_FROM_WIN32(ERROR_RETRY),
"Failed to capture local clipboard files: "
"stage=sequence-before-OleGetClipboard expected=%lu actual=%lu",
static_cast<unsigned long>(sequence),
static_cast<unsigned long>(oleSequence));
return nullptr; return nullptr;
} }
@@ -3283,9 +3494,14 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, bool& viaOLE,
{ {
if (SUCCEEDED(oleError)) if (SUCCEEDED(oleError))
oleError = E_UNEXPECTED; oleError = E_UNEXPECTED;
if (oleError == CLIPBRD_E_CANT_OPEN)
retryStage = "OleGetClipboard";
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: "
"stage=OleGetClipboard sequence=%lu rawFormats=%d",
static_cast<unsigned long>(sequence), rawFormatCount);
return nullptr; return nullptr;
} }
openedOLE = true;
FORMATETC format = { FORMATETC format = {
static_cast<CLIPFORMAT>(CF_HDROP), static_cast<CLIPFORMAT>(CF_HDROP),
@@ -3295,40 +3511,258 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, bool& viaOLE,
TYMED_HGLOBAL, TYMED_HGLOBAL,
}; };
ClipboardStorageScope storage; ClipboardStorageScope storage;
oleError = object.object->GetData(&format, &storage.medium); const HRESULT hdropError = object.object->GetData(
if (FAILED(oleError)) &format, &storage.medium);
if (FAILED(hdropError))
{
DEBUG_ERROR_HR(hdropError,
"Failed to capture local clipboard files: "
"stage=IDataObject::GetData(CF_HDROP) sequence=%lu rawFormats=%d",
static_cast<unsigned long>(sequence), rawFormatCount);
}
if (hdropError == CLIPBRD_E_CANT_OPEN)
{
oleError = hdropError;
retryStage = "IDataObject::GetData(CF_HDROP)";
return nullptr; return nullptr;
storage.acquired = true; }
std::shared_ptr<CLocalClipboardFiles> files; if (SUCCEEDED(hdropError))
{
storage.acquired = true;
if (storage.medium.tymed == TYMED_HGLOBAL && storage.medium.hGlobal) if (storage.medium.tymed == TYMED_HGLOBAL && storage.medium.hGlobal)
{ {
SetLastError(ERROR_SUCCESS); std::shared_ptr<CLocalClipboardFiles> files =
try CLocalClipboardFiles::Capture(
{
files = CLocalClipboardFiles::Capture(
static_cast<HDROP>(storage.medium.hGlobal), error); static_cast<HDROP>(storage.medium.hGlobal), error);
if (!files) if (files)
winError = GetLastError();
}
catch (const std::bad_alloc&)
{ {
winError = ERROR_OUTOFMEMORY; viaOLE = true;
error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; oleError = S_OK;
return files;
} }
catch (const std::length_error&) oleError = ClipboardFileHRESULT(error);
{ DEBUG_ERROR_HR(oleError,
winError = ERROR_OUTOFMEMORY; "Failed to capture local clipboard files: "
error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; "stage=CLocalClipboardFiles::Capture(CF_HDROP/ole) sequence=%lu "
"rawFormats=%d fileError=%u",
static_cast<unsigned long>(sequence), rawFormatCount,
static_cast<unsigned int>(error));
return nullptr;
} }
} }
else
if (SUCCEEDED(hdropError))
{ {
oleError = DV_E_TYMED; oleError = DV_E_TYMED;
error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID;
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: "
"stage=IDataObject::GetData(CF_HDROP)/STGMEDIUM sequence=%lu "
"rawFormats=%d tymed=0x%08lx hasHGlobal=%u",
static_cast<unsigned long>(sequence), rawFormatCount,
static_cast<unsigned long>(storage.medium.tymed),
storage.medium.hGlobal ? 1U : 0U);
return nullptr;
} }
if (files)
ClipboardComScope<IShellItemArray> items;
const HRESULT shellError = SHCreateShellItemArrayFromDataObject(object.object,
IID_IShellItemArray, reinterpret_cast<void **>(&items.object));
if (FAILED(shellError))
{
DEBUG_ERROR_HR(shellError,
"Failed to capture local clipboard files: "
"stage=SHCreateShellItemArrayFromDataObject sequence=%lu",
static_cast<unsigned long>(sequence));
}
else if (!items.object)
{
DEBUG_ERROR_HR(E_UNEXPECTED,
"Failed to capture local clipboard files: "
"stage=SHCreateShellItemArrayFromDataObject sequence=%lu "
"result=null",
static_cast<unsigned long>(sequence));
}
if (FAILED(shellError) || !items.object)
{
oleError = FAILED(shellError) ? shellError : E_UNEXPECTED;
if (oleError == CLIPBRD_E_CANT_OPEN)
retryStage = "SHCreateShellItemArrayFromDataObject";
HRESULT enumError = S_OK;
const bool fileCandidate = win32Candidate ||
hdropError != DV_E_FORMATETC ||
ClipboardDataObjectHasFileFormats(object.object, sequence, enumError);
if (!fileCandidate && SUCCEEDED(enumError) &&
oleError != CLIPBRD_E_CANT_OPEN)
{
error = KVMFR_CLIPBOARD_FILE_ERROR_NONE;
oleError = S_FALSE;
return nullptr;
}
return nullptr;
}
DWORD count = 0;
oleError = items.object->GetCount(&count);
if (FAILED(oleError) || !count)
{
if (SUCCEEDED(oleError))
oleError = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: "
"stage=IShellItemArray::GetCount sequence=%lu count=%lu",
static_cast<unsigned long>(sequence),
static_cast<unsigned long>(count));
return nullptr;
}
std::vector<std::wstring> paths;
size_t characters = 1U;
try
{
paths.reserve(count);
for (DWORD index = 0; index < count; ++index)
{
ClipboardComScope<IShellItem> item;
oleError = items.object->GetItemAt(index, &item.object);
if (FAILED(oleError) || !item.object)
{
if (SUCCEEDED(oleError))
oleError = E_UNEXPECTED;
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: "
"stage=IShellItemArray::GetItemAt sequence=%lu item=%lu",
static_cast<unsigned long>(sequence),
static_cast<unsigned long>(index));
return nullptr;
}
ClipboardTaskStringScope path;
oleError = item.object->GetDisplayName(
SIGDN_FILESYSPATH, &path.value);
if (FAILED(oleError) || !path.value || !path.value[0])
{
if (SUCCEEDED(oleError))
oleError = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: "
"stage=IShellItem::GetDisplayName sequence=%lu item=%lu",
static_cast<unsigned long>(sequence),
static_cast<unsigned long>(index));
return nullptr;
}
const size_t length = wcslen(path.value);
if (length > (std::numeric_limits<size_t>::max)() - characters - 1U)
throw std::length_error("clipboard path list");
characters += length + 1U;
paths.emplace_back(path.value, length);
}
}
catch (const std::bad_alloc&)
{
oleError = E_OUTOFMEMORY;
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: stage=Shell-path-list "
"sequence=%lu", static_cast<unsigned long>(sequence));
return nullptr;
}
catch (const std::length_error&)
{
oleError = HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: stage=Shell-path-list "
"sequence=%lu", static_cast<unsigned long>(sequence));
return nullptr;
}
if (characters > ((std::numeric_limits<SIZE_T>::max)() -
sizeof(DROPFILES)) / sizeof(wchar_t))
{
oleError = HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: stage=DROPFILES-size "
"sequence=%lu", static_cast<unsigned long>(sequence));
return nullptr;
}
const SIZE_T dropBytes = sizeof(DROPFILES) +
characters * sizeof(wchar_t);
SetLastError(ERROR_SUCCESS);
HGLOBAL drop = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dropBytes);
if (!drop)
{
const DWORD allocError = GetLastError();
oleError = allocError ? HRESULT_FROM_WIN32(allocError) : E_OUTOFMEMORY;
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: stage=GlobalAlloc(DROPFILES) "
"sequence=%lu", static_cast<unsigned long>(sequence));
return nullptr;
}
SetLastError(ERROR_SUCCESS);
DROPFILES * header = static_cast<DROPFILES *>(GlobalLock(drop));
if (!header)
{
const DWORD lockError = GetLastError();
oleError = lockError ? HRESULT_FROM_WIN32(lockError) : E_UNEXPECTED;
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: stage=GlobalLock(DROPFILES) "
"sequence=%lu", static_cast<unsigned long>(sequence));
SetLastError(ERROR_SUCCESS);
HGLOBAL freeResult = GlobalFree(drop);
if (freeResult)
{
const DWORD freeError = GetLastError();
DEBUG_ERROR_HR(freeError ? HRESULT_FROM_WIN32(freeError) : E_UNEXPECTED,
"Failed to capture local clipboard files: "
"stage=GlobalFree(DROPFILES) sequence=%lu",
static_cast<unsigned long>(sequence));
}
return nullptr;
}
header->pFiles = sizeof(*header);
header->fWide = TRUE;
wchar_t * output = reinterpret_cast<wchar_t *>(
reinterpret_cast<uint8_t *>(header) + sizeof(*header));
for (const std::wstring& path : paths)
{
memcpy(output, path.c_str(), (path.size() + 1U) * sizeof(wchar_t));
output += path.size() + 1U;
}
SetLastError(ERROR_SUCCESS);
const BOOL unlocked = GlobalUnlock(drop);
const DWORD unlockError = unlocked ? ERROR_SUCCESS : GetLastError();
if (!unlocked && unlockError != ERROR_SUCCESS)
{
DEBUG_ERROR_HR(HRESULT_FROM_WIN32(unlockError),
"Failed to capture local clipboard files: "
"stage=GlobalUnlock(DROPFILES) sequence=%lu",
static_cast<unsigned long>(sequence));
}
files = CLocalClipboardFiles::Capture(static_cast<HDROP>(drop), error);
if (!files)
{
oleError = ClipboardFileHRESULT(error);
DEBUG_ERROR_HR(oleError,
"Failed to capture local clipboard files: "
"stage=CLocalClipboardFiles::Capture(Shell items) sequence=%lu "
"fileError=%u", static_cast<unsigned long>(sequence),
static_cast<unsigned int>(error));
}
SetLastError(ERROR_SUCCESS);
HGLOBAL freeResult = GlobalFree(drop);
if (freeResult)
{
const DWORD freeError = GetLastError();
DEBUG_ERROR_HR(freeError ? HRESULT_FROM_WIN32(freeError) : E_UNEXPECTED,
"Failed to capture local clipboard files: "
"stage=GlobalFree(DROPFILES) sequence=%lu",
static_cast<unsigned long>(sequence));
}
if (!files)
return nullptr;
viaOLE = true; viaOLE = true;
oleError = S_OK;
return files; return files;
} }
@@ -3406,22 +3840,27 @@ void CClipboardManager::PublishLocalClipboard()
KVMFRClipboardFileError fileError = KVMFRClipboardFileError fileError =
KVMFR_CLIPBOARD_FILE_ERROR_INVALID; KVMFR_CLIPBOARD_FILE_ERROR_INVALID;
DWORD fileWinError = ERROR_SUCCESS;
HRESULT fileOleError = S_FALSE; HRESULT fileOleError = S_FALSE;
bool fileViaOLE = false; bool fileViaOLE = false;
bool fileOpenedOLE = false; const char * fileRetryStage = nullptr;
std::shared_ptr<CLocalClipboardFiles> files = CaptureClipboardFiles( std::shared_ptr<CLocalClipboardFiles> files = CaptureClipboardFiles(
after, fileViaOLE, fileOpenedOLE, fileError, fileWinError, after, rawFormatCount, fileViaOLE, fileRetryStage, fileError,
fileOleError); fileOleError);
if (GetClipboardSequenceNumber() != after) const DWORD capturedSequence = GetClipboardSequenceNumber();
if (capturedSequence != after)
{ {
DEBUG_ERROR_HR(HRESULT_FROM_WIN32(ERROR_RETRY),
"Failed to publish local clipboard: "
"stage=sequence-after-file-capture expected=%lu actual=%lu",
static_cast<unsigned long>(after),
static_cast<unsigned long>(capturedSequence));
ClearLocalRetry(); ClearLocalRetry();
PostMessageW(m_hwnd, WM_CLIPBOARDUPDATE, 0, 0); PostMessageW(m_hwnd, WM_CLIPBOARDUPDATE, 0, 0);
return; return;
} }
if (fileOleError == CLIPBRD_E_CANT_OPEN) if (fileOleError == CLIPBRD_E_CANT_OPEN)
{ {
DeferLocalClipboard(after, fileOpenedOLE); DeferLocalClipboard(after, fileRetryStage);
return; return;
} }
ClearLocalRetry(); ClearLocalRetry();
@@ -3457,36 +3896,16 @@ void CClipboardManager::PublishLocalClipboard()
} }
else else
{ {
DEBUG_WARN("Failed to retain local clipboard file selection: " DEBUG_ERROR_HR(E_OUTOFMEMORY,
"sequence=%lu fileError=%u winError=%lu", "Failed to capture local clipboard files: stage=retain-dataset "
static_cast<unsigned long>(after), "sequence=%lu fileError=%u",
static_cast<unsigned int>(KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY), static_cast<unsigned long>(after), static_cast<unsigned int>(
static_cast<unsigned long>(ERROR_OUTOFMEMORY)); KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY));
formats &= ~KVMFR_CLIPBOARD_FORMAT_MASK_FILES; formats &= ~KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
} }
} }
else else
{
formats &= ~KVMFR_CLIPBOARD_FORMAT_MASK_FILES; formats &= ~KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
if ((recognizedFormats & KVMFR_CLIPBOARD_FORMAT_MASK_FILES) ||
SUCCEEDED(fileOleError))
{
DEBUG_WARN("Failed to capture local clipboard file selection: "
"sequence=%lu fileError=%u winError=%lu oleError=0x%08lx",
static_cast<unsigned long>(after),
static_cast<unsigned int>(fileError),
static_cast<unsigned long>(fileWinError),
static_cast<unsigned long>(fileOleError));
}
else if (FAILED(fileOleError) && fileOleError != DV_E_FORMATETC &&
fileOleError != DATA_E_FORMATETC)
{
DEBUG_TRACE_HR(fileOleError,
"OLE clipboard does not expose local files: sequence=%lu "
"rawFormats=%d", static_cast<unsigned long>(after),
rawFormatCount);
}
}
DEBUG_INFO("Local clipboard update: sequence=%lu rawFormats=%d " DEBUG_INFO("Local clipboard update: sequence=%lu rawFormats=%d "
"recognized=0x%08x effective=0x%08x", "recognized=0x%08x effective=0x%08x",
static_cast<unsigned long>(after), rawFormatCount, static_cast<unsigned long>(after), rawFormatCount,
@@ -3573,7 +3992,8 @@ void CClipboardManager::ClearLocalRetry()
m_localRetryDeadline = 0; m_localRetryDeadline = 0;
} }
void CClipboardManager::DeferLocalClipboard(DWORD sequence, bool openedOLE) void CClipboardManager::DeferLocalClipboard(DWORD sequence,
const char * stage)
{ {
const uint64_t now = GetTickCount64(); const uint64_t now = GetTickCount64();
if (!m_localRetryDeadline || m_localRetrySequence != sequence) if (!m_localRetryDeadline || m_localRetrySequence != sequence)
@@ -3583,7 +4003,7 @@ void CClipboardManager::DeferLocalClipboard(DWORD sequence, bool openedOLE)
m_localRetryDeadline = now + LOCAL_RETRY_TIMEOUT_MS; m_localRetryDeadline = now + LOCAL_RETRY_TIMEOUT_MS;
DEBUG_TRACE("Clipboard is busy; deferring local publication: " DEBUG_TRACE("Clipboard is busy; deferring local publication: "
"sequence=%lu stage=%s", static_cast<unsigned long>(sequence), "sequence=%lu stage=%s", static_cast<unsigned long>(sequence),
openedOLE ? "IDataObject::GetData" : "OleGetClipboard"); stage ? stage : "unknown");
} }
if (now < m_localRetryDeadline && if (now < m_localRetryDeadline &&

View File

@@ -295,12 +295,14 @@ private:
void RetryLocalClipboard(); void RetryLocalClipboard();
void RetryRemoteOffer(); void RetryRemoteOffer();
bool OpenClipboardRetry() const; bool OpenClipboardRetry(DWORD * error = nullptr,
const char * stage = nullptr, bool useWindow = true) const;
bool IsOurClipboard(); bool IsOurClipboard();
uint32_t EnumerateFormats() const; uint32_t EnumerateFormats() const;
std::shared_ptr<CLocalClipboardFiles> CaptureClipboardFiles( std::shared_ptr<CLocalClipboardFiles> CaptureClipboardFiles(
DWORD sequence, bool& viaOLE, bool& openedOLE, DWORD sequence, int rawFormatCount, bool& viaOLE,
KVMFRClipboardFileError& error, DWORD& winError, HRESULT& oleError); const char *& retryStage, KVMFRClipboardFileError& error,
HRESULT& oleError);
void PublishLocalClipboard(); void PublishLocalClipboard();
void PublishClear(uint64_t generation); void PublishClear(uint64_t generation);
bool ApplyRemoteOffer(uint32_t formats, uint64_t generation); bool ApplyRemoteOffer(uint32_t formats, uint64_t generation);
@@ -308,7 +310,7 @@ private:
void InvalidateOutgoing(uint32_t reason); void InvalidateOutgoing(uint32_t reason);
void InvalidateLocalClipboard(uint32_t reason); void InvalidateLocalClipboard(uint32_t reason);
void ClearLocalRetry(); void ClearLocalRetry();
void DeferLocalClipboard(DWORD sequence, bool openedOLE); void DeferLocalClipboard(DWORD sequence, const char * stage);
void ExpireLocalRetry(DWORD sequence); void ExpireLocalRetry(DWORD sequence);
void ClearRemoteRetry(); void ClearRemoteRetry();
bool SetOriginMarker(uint64_t generation) const; bool SetOriginMarker(uint64_t generation) const;

View File

@@ -137,8 +137,8 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
const OleScope oleScope; const OleScope oleScope;
const HRESULT security = CoInitializeSecurity(nullptr, 0, nullptr, nullptr, const HRESULT security = CoInitializeSecurity(nullptr, 0, nullptr, nullptr,
RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_NONE, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IDENTIFY, nullptr,
nullptr); EOAC_DYNAMIC_CLOAKING, nullptr);
if (FAILED(security)) if (FAILED(security))
{ {
DEBUG_ERROR_HR(security, "Failed to initialize COM security"); DEBUG_ERROR_HR(security, "Failed to initialize COM security");