Add system clipboard support to Windows

This commit is contained in:
elasota
2020-12-18 00:07:21 -05:00
parent fc043af3a1
commit 83d37a7c94
13 changed files with 508 additions and 2 deletions

View File

@@ -1,14 +1,117 @@
#include "GpSystemServices_Win32.h"
#include "GpMutex_Win32.h"
#include "GpThreadEvent_Win32.h"
#include "GpWindows.h"
#include "IGpClipboardContents.h"
#include "UTF16.h"
#include "UTF8.h"
#include <assert.h>
#include <vector>
#pragma push_macro("CreateMutex")
#ifdef CreateMutex
#undef CreateMutex
#endif
extern GpWindowsGlobals g_gpWindowsGlobals;
namespace GpSystemServices_Win32_Private
{
class RefCountedClipboard
{
public:
RefCountedClipboard();
protected:
virtual ~RefCountedClipboard();
void AddRef();
void DecRef();
unsigned int m_refCount;
};
class TextClipboard : public RefCountedClipboard, public IGpClipboardContentsText
{
public:
TextClipboard(const uint8_t *utf8Text, size_t utf8Size);
~TextClipboard() override;
GpClipboardContentsType_t GetContentsType() const override;
void Destroy() override;
IGpClipboardContents *Clone() const override;
const uint8_t *GetBytes() const override;
size_t GetSize() const override;
private:
std::vector<uint8_t> m_utf8Text;
};
RefCountedClipboard::RefCountedClipboard()
: m_refCount(1)
{
}
RefCountedClipboard::~RefCountedClipboard()
{
}
void RefCountedClipboard::AddRef()
{
m_refCount++;
}
void RefCountedClipboard::DecRef()
{
unsigned int rc = --m_refCount;
if (rc == 0)
delete this;
}
TextClipboard::TextClipboard(const uint8_t *utf8Text, size_t utf8Size)
{
m_utf8Text.resize(utf8Size);
if (utf8Size > 0)
memcpy(&m_utf8Text[0], utf8Text, utf8Size);
}
TextClipboard::~TextClipboard()
{
}
GpClipboardContentsType_t TextClipboard::GetContentsType() const
{
return GpClipboardContentsTypes::kText;
}
void TextClipboard::Destroy()
{
this->DecRef();
}
IGpClipboardContents *TextClipboard::Clone() const
{
const_cast<TextClipboard*>(this)->AddRef();
return const_cast<TextClipboard*>(this);
}
const uint8_t *TextClipboard::GetBytes() const
{
if (m_utf8Text.size() == 0)
return nullptr;
return &m_utf8Text[0];
}
size_t TextClipboard::GetSize() const
{
return m_utf8Text.size();
}
}
struct GpSystemServices_Win32_ThreadStartParams
{
GpSystemServices_Win32::ThreadFunc_t m_threadFunc;
@@ -34,6 +137,10 @@ GpSystemServices_Win32::GpSystemServices_Win32()
{
}
GpSystemServices_Win32::~GpSystemServices_Win32()
{
}
int64_t GpSystemServices_Win32::GetTime() const
{
SYSTEMTIME epochStart;
@@ -171,6 +278,89 @@ bool GpSystemServices_Win32::AreFontResourcesSeekable() const
return true;
}
IGpClipboardContents *GpSystemServices_Win32::GetClipboardContents() const
{
IGpClipboardContents *cbObject = nullptr;
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
{
if (OpenClipboard(g_gpWindowsGlobals.m_hwnd))
{
HGLOBAL textHandle = GetClipboardData(CF_UNICODETEXT);
if (textHandle)
{
const wchar_t *str = static_cast<const wchar_t*>(GlobalLock(textHandle));
if (str)
{
if (str[0] == 0)
cbObject = new GpSystemServices_Win32_Private::TextClipboard(nullptr, 0);
else
{
int bytesRequired = WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, nullptr, nullptr);
if (bytesRequired > 0)
{
std::vector<char> decodedText;
decodedText.resize(bytesRequired);
WideCharToMultiByte(CP_UTF8, 0, str, -1, &decodedText[0], bytesRequired, nullptr, nullptr);
cbObject = new GpSystemServices_Win32_Private::TextClipboard(reinterpret_cast<const uint8_t*>(&decodedText[0]), decodedText.size() - 1);
}
}
GlobalUnlock(textHandle);
}
}
CloseClipboard();
}
}
return cbObject;
}
void GpSystemServices_Win32::SetClipboardContents(IGpClipboardContents *contents)
{
if (!contents)
return;
if (contents->GetContentsType() == GpClipboardContentsTypes::kText)
{
IGpClipboardContentsText *textContents = static_cast<IGpClipboardContentsText*>(contents);
if (OpenClipboard(g_gpWindowsGlobals.m_hwnd))
{
if (EmptyClipboard())
{
int wcharsRequired = MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<const char*>(textContents->GetBytes()), textContents->GetSize(), nullptr, 0);
std::vector<wchar_t> wideChars;
if (wcharsRequired)
{
wideChars.resize(wcharsRequired + 1);
MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<const char*>(textContents->GetBytes()), textContents->GetSize(), &wideChars[0], wcharsRequired);
}
else
wideChars.resize(1);
wideChars[wideChars.size() - 1] = static_cast<wchar_t>(0);
HGLOBAL textObject = GlobalAlloc(GMEM_MOVEABLE, wideChars.size() * sizeof(wchar_t));
if (textObject)
{
wchar_t *buffer = static_cast<wchar_t*>(GlobalLock(textObject));
memcpy(buffer, &wideChars[0], wideChars.size() * sizeof(wchar_t));
GlobalUnlock(textObject);
SetClipboardData(CF_UNICODETEXT, textObject);
}
}
CloseClipboard();
}
}
}
void GpSystemServices_Win32::SetTouchscreenSimulation(bool isTouchscreenSimulation)
{

View File

@@ -19,6 +19,7 @@ class GpSystemServices_Win32 final : public IGpSystemServices
{
public:
GpSystemServices_Win32();
~GpSystemServices_Win32();
int64_t GetTime() const override;
void GetLocalDateTime(unsigned int &year, unsigned int &month, unsigned int &day, unsigned int &hour, unsigned int &minute, unsigned int &second) const override;
@@ -36,6 +37,8 @@ public:
void SetTextInputEnabled(bool isEnabled) override;
bool IsTextInputEnabled() const override;
bool AreFontResourcesSeekable() const override;
IGpClipboardContents *GetClipboardContents() const override;
void SetClipboardContents(IGpClipboardContents *contents) override;
void SetTouchscreenSimulation(bool isTouchscreenSimulation);

View File

@@ -290,6 +290,15 @@ bool GpSystemServices_Android::AreFontResourcesSeekable() const
return false;
}
IGpClipboardContents *GpSystemServices_Android::GetClipboardContents() const
{
return nullptr;
}
void GpSystemServices_Android::SetClipboardContents(IGpClipboardContents *contents)
{
}
GpSystemServices_Android *GpSystemServices_Android::GetInstance()
{
return &ms_instance;

View File

@@ -24,6 +24,8 @@ public:
void SetTextInputEnabled(bool isEnabled) override;
bool IsTextInputEnabled() const override;
bool AreFontResourcesSeekable() const override;
IGpClipboardContents *GetClipboardContents() const override;
void SetClipboardContents(IGpClipboardContents *contents) override;
void FlushTextInputEnabled();

View File

@@ -0,0 +1,11 @@
#pragma once
namespace GpClipboardContentsTypes
{
enum GpClipboardContentsType
{
kText,
};
}
typedef GpClipboardContentsTypes::GpClipboardContentsType GpClipboardContentsType_t;

View File

@@ -0,0 +1,16 @@
#include "GpClipboardContentsType.h"
#include <stdint.h>
struct IGpClipboardContents
{
virtual GpClipboardContentsType_t GetContentsType() const = 0;
virtual void Destroy() = 0;
virtual IGpClipboardContents *Clone() const = 0;
};
struct IGpClipboardContentsText : public IGpClipboardContents
{
virtual const uint8_t *GetBytes() const = 0;
virtual size_t GetSize() const = 0; // In bytes
};

View File

@@ -12,6 +12,7 @@
struct IGpMutex;
struct IGpThreadEvent;
struct IGpClipboardContents;
struct IGpSystemServices
{
@@ -34,4 +35,6 @@ public:
virtual void SetTextInputEnabled(bool isEnabled) = 0;
virtual bool IsTextInputEnabled() const = 0;
virtual bool AreFontResourcesSeekable() const = 0;
virtual IGpClipboardContents *GetClipboardContents() const = 0;
virtual void SetClipboardContents(IGpClipboardContents *contents) = 0;
};

View File

@@ -109,7 +109,8 @@ namespace PortabilityLayer
uint8_t *bytes = static_cast<uint8_t*>(buf);
const MMBlock *mmBlock = reinterpret_cast<const MMBlock*>(bytes - MMBlock::AlignedSize());
free(bytes - MMBlock::AlignedSize() - mmBlock->m_offsetFromAllocLocation);
void *freeLoc = bytes - MMBlock::AlignedSize() - mmBlock->m_offsetFromAllocLocation;
free(freeLoc);
}
MMHandleBlock *MemoryManagerImpl::AllocHandle(size_t size)

View File

@@ -15,6 +15,7 @@
#include "IGpDirectoryCursor.h"
#include "HostSuspendCallArgument.h"
#include "HostSuspendHook.h"
#include "IGpClipboardContents.h"
#include "IGpCursor.h"
#include "IGpDisplayDriver.h"
#include "IGpFileSystem.h"
@@ -45,6 +46,8 @@
#include "PLTimeTaggedVOSEvent.h"
#include "PLWidgets.h"
#include "UTF8.h"
#include <assert.h>
#include <algorithm>
@@ -688,6 +691,141 @@ WindowPtr PL_GetPutInFrontWindowPtr()
return PortabilityLayer::WindowManager::GetInstance()->GetPutInFrontSentinel();
}
class PLClipboardContentsText : public IGpClipboardContentsText
{
public:
static PLClipboardContentsText *CreateFromMacRomanStr(const uint8_t *chars, size_t size);
GpClipboardContentsType_t GetContentsType() const override;
void Destroy() override;
IGpClipboardContents *Clone() const override;
const uint8_t *GetBytes() const override;
size_t GetSize() const override;
private:
PLClipboardContentsText(uint8_t *utf8Bytes, size_t size);
~PLClipboardContentsText();
uint8_t *m_utf8Bytes;
size_t m_size;
};
PLClipboardContentsText *PLClipboardContentsText::CreateFromMacRomanStr(const uint8_t *chars, size_t length)
{
PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance();
size_t numUTF8Bytes = 0;
for (size_t i = 0; i < length; i++)
{
uint8_t utf8Bytes[PortabilityLayer::UTF8Processor::kMaxEncodedBytes];
uint16_t codePoint = MacRoman::ToUnicode(chars[i]);
size_t numBytesEmitted = 0;
PortabilityLayer::UTF8Processor::EncodeCodePoint(utf8Bytes, numBytesEmitted, codePoint);
numUTF8Bytes += numBytesEmitted;
}
uint8_t *utf8Bytes = nullptr;
if (numUTF8Bytes)
{
utf8Bytes = static_cast<uint8_t*>(mm->Alloc(numUTF8Bytes));
if (!utf8Bytes)
return nullptr;
numUTF8Bytes = 0;
for (size_t i = 0; i < length; i++)
{
uint16_t codePoint = MacRoman::ToUnicode(chars[i]);
size_t numBytesEmitted = 0;
PortabilityLayer::UTF8Processor::EncodeCodePoint(utf8Bytes + numUTF8Bytes, numBytesEmitted, codePoint);
numUTF8Bytes += numBytesEmitted;
}
}
void *storage = mm->Alloc(sizeof(PLClipboardContentsText));
if (!storage)
{
mm->Release(utf8Bytes);
return nullptr;
}
return new (storage) PLClipboardContentsText(utf8Bytes, numUTF8Bytes);
}
PLClipboardContentsText::PLClipboardContentsText(uint8_t *utf8Bytes, size_t size)
: m_utf8Bytes(utf8Bytes)
, m_size(size)
{
}
PLClipboardContentsText::~PLClipboardContentsText()
{
PortabilityLayer::MemoryManager::GetInstance()->Release(m_utf8Bytes);
}
GpClipboardContentsType_t PLClipboardContentsText::GetContentsType() const
{
return GpClipboardContentsTypes::kText;
}
void PLClipboardContentsText::Destroy()
{
this->~PLClipboardContentsText();
PortabilityLayer::MemoryManager::GetInstance()->Release(this);
}
IGpClipboardContents *PLClipboardContentsText::Clone() const
{
PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance();
uint8_t *bytesCopy = nullptr;
if (m_size)
{
bytesCopy = static_cast<uint8_t*>(mm->Alloc(m_size));
if (!bytesCopy)
return nullptr;
memcpy(bytesCopy, m_utf8Bytes, m_size);
}
void *storage = mm->Alloc(sizeof(PLClipboardContentsText));
if (!storage)
{
if (bytesCopy)
mm->Release(bytesCopy);
return nullptr;
}
return new (storage) PLClipboardContentsText(bytesCopy, m_size);
}
const uint8_t *PLClipboardContentsText::GetBytes() const
{
return m_utf8Bytes;
}
size_t PLClipboardContentsText::GetSize() const
{
return m_size;
}
void PL_CopyStringToClipboard(const uint8_t *chars, size_t length)
{
if (IGpClipboardContentsText *clipboardText = PLClipboardContentsText::CreateFromMacRomanStr(chars, length))
{
PLDrivers::GetSystemServices()->SetClipboardContents(clipboardText);
clipboardText->Destroy();
}
}
Window::Window()
: m_surface(PortabilityLayer::QDPortType_Window)
, m_wmX(0)

View File

@@ -279,3 +279,5 @@ void PL_NotYetImplemented();
void PL_NotYetImplemented_Minor();
void PL_NotYetImplemented_TODO(const char *category);
void PL_Init();
void PL_CopyStringToClipboard(const uint8_t *chars, size_t length);

View File

@@ -2,6 +2,7 @@
#include "FontFamily.h"
#include "FontManager.h"
#include "IGpClipboardContents.h"
#include "IGpSystemServices.h"
#include "InputManager.h"
#include "MacRomanConversion.h"
@@ -9,14 +10,16 @@
#include "RenderedFont.h"
#include "GpRenderedFontMetrics.h"
#include "ResolveCachingColor.h"
#include "TextPlacer.h"
#include "Rect2i.h"
#include "TextPlacer.h"
#include "UTF8.h"
#include "PLDrivers.h"
#include "PLKeyEncoding.h"
#include "PLQDraw.h"
#include "PLStandardColors.h"
#include "PLTimeTaggedVOSEvent.h"
#include "PLCore.h"
#include <algorithm>
@@ -237,6 +240,7 @@ namespace PortabilityLayer
const KeyDownStates *downStates = PortabilityLayer::InputManager::GetInstance()->GetKeys();
const bool isShiftHeld = downStates->m_special.Get(GpKeySpecials::kLeftShift) || downStates->m_special.Get(GpKeySpecials::kRightShift);
const bool isWords = downStates->m_special.Get(GpKeySpecials::kLeftCtrl) || downStates->m_special.Get(GpKeySpecials::kRightCtrl);
const bool isCtrlHeld = downStates->m_special.Get(GpKeySpecials::kLeftCtrl) || downStates->m_special.Get(GpKeySpecials::kRightCtrl);
if (keyEvent.m_keyIDSubset == GpKeyIDSubsets::kSpecial)
{
@@ -281,6 +285,39 @@ namespace PortabilityLayer
return WidgetHandleStates::kDigested;
}
}
if (isCtrlHeld && keyEvent.m_keyIDSubset == GpKeyIDSubsets::kASCII)
{
if (keyEvent.m_key.m_asciiChar == kCopyShortcutKey)
{
if (m_selStartChar != m_selEndChar)
HandleCopy();
return WidgetHandleStates::kDigested;
}
if (keyEvent.m_key.m_asciiChar == kCutShortcutKey)
{
if (m_selStartChar != m_selEndChar)
HandleCut();
return WidgetHandleStates::kDigested;
}
else if (keyEvent.m_key.m_asciiChar == kPasteShortcutKey)
{
HandlePaste(keyEvent.m_repeatCount);
return WidgetHandleStates::kDigested;
}
else if (keyEvent.m_key.m_asciiChar == kSelectAllShortcutKey)
{
m_selStartChar = 0;
m_selEndChar = m_length;
m_caratSelectionAnchor = CaratSelectionAnchor_Start;
m_caratScrollLocked = false;
AdjustScrollToCarat();
m_caratTimer = 0;
Redraw();
}
}
}
}
else if (evt.m_vosEvent.m_eventType == GpVOSEventTypes::kMouseInput)
@@ -642,7 +679,91 @@ namespace PortabilityLayer
Redraw();
}
}
void EditboxWidget::HandleCopy()
{
if (m_selStartChar == m_selEndChar)
return;
PL_CopyStringToClipboard(m_chars + m_selStartChar, m_selEndChar - m_selStartChar);
}
void EditboxWidget::HandleCut()
{
HandleCopy();
HandleBackspace(1);
}
void EditboxWidget::HandlePaste(size_t repeatCount)
{
IGpClipboardContents *clipboardContents = PLDrivers::GetSystemServices()->GetClipboardContents();
if (clipboardContents == nullptr)
return;
if (clipboardContents->GetContentsType() != GpClipboardContentsTypes::kText)
{
clipboardContents->Destroy();
return;
}
IGpClipboardContentsText *textContents = static_cast<IGpClipboardContentsText*>(clipboardContents);
const size_t utf8Size = textContents->GetSize();
const uint8_t *utf8Bytes = textContents->GetBytes();
size_t numCodePoints = 0;
for (size_t i = 0; i < utf8Size; )
{
uint32_t codePoint = 0;
size_t numDigested = 0;
if (!UTF8Processor::DecodeCodePoint(utf8Bytes + i, utf8Size - i, numDigested, codePoint))
{
clipboardContents->Destroy();
return;
}
i += numDigested;
numCodePoints++;
}
PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance();
uint8_t *decodedChars = static_cast<uint8_t*>(mm->Alloc(numCodePoints));
if (!decodedChars)
{
clipboardContents->Destroy();
return;
}
numCodePoints = 0;
for (size_t i = 0; i < utf8Size; )
{
uint32_t codePoint = 0;
size_t numDigested = 0;
if (!UTF8Processor::DecodeCodePoint(utf8Bytes + i, utf8Size - i, numDigested, codePoint))
{
clipboardContents->Destroy();
return;
}
if (codePoint > 0xffff || !MacRoman::FromUnicode(decodedChars[numCodePoints], static_cast<uint16_t>(codePoint)))
decodedChars[numCodePoints] = '?';
numCodePoints++;
i += numDigested;
}
// This is extremely suboptimal due to the embedded redraw...
for (size_t i = 0; i < repeatCount; i++)
{
for (size_t j = 0; j < numCodePoints; j++)
HandleCharacter(decodedChars[j], 1);
}
mm->Release(decodedChars);
clipboardContents->Destroy();
}
void EditboxWidget::HandleRightArrow(const uint32_t numRepeatsRequested, bool shiftHeld, bool wholeWords)
{

View File

@@ -90,6 +90,9 @@ namespace PortabilityLayer
void HandleHome(bool shiftHeld);
void HandleEnd(bool shiftHeld);
void HandleCopy();
void HandleCut();
void HandlePaste(size_t repeatCount);
size_t FindVerticalMovementCaratPos(const Vec2i &desiredPos, bool &isOutOfRange, CaratCharacterAlignment *optOutAlignment) const;
void ExpandSelectionToWords(size_t caratPos, size_t &outStartChar, size_t &outEndChar);
@@ -137,5 +140,10 @@ namespace PortabilityLayer
void *m_characterFilterContext;
static const CharacterCategorySpan gs_characterCategorySpans[];
static const int kCopyShortcutKey = 'C';
static const int kCutShortcutKey = 'X';
static const int kPasteShortcutKey = 'V';
static const int kSelectAllShortcutKey = 'A';
};
}

View File

@@ -11,5 +11,7 @@ namespace PortabilityLayer
static void EncodeCodePoint(uint8_t *characters, size_t &outCharactersEmitted, uint32_t codePoint);
static bool DecodeToMacRomanPascalStr(const uint8_t *inChars, size_t inSize, uint8_t *outChars, size_t maxOutSize, size_t &outSize);
static const unsigned int kMaxEncodedBytes = 4;
};
}