Add clipboard support #216
lc-soft posted onGitHub
Is your feature request related to a problem? Please describe.
Sometimes we need to paste some content into the input box, but LCUI does not support it.
Describe the solution you'd like
Add clipboard support to meet the following requirements:
- textedit: CTRL+V should be able to insert the content of the clipboard to the caret position.
- Should work on Linux.
- Should work on Windows.
Describe alternatives you've considered
None.
Additional context
You can refer to the source code of SDL:
- https://github.com/SDL-mirror/SDL/blob/master/src/video/SDL_clipboard.c
- https://github.com/SDL-mirror/SDL/blob/master/src/video/windows/SDL_windowsclipboard.c
- https://github.com/SDL-mirror/SDL/blob/master/src/video/x11/SDL_x11clipboard.c
Note: If you think you can do it, please request a bounty on issuehunt.io to tell everyone how much bounty you need. If you think this feature is necessary, you can also fund this issue on issuehunt.io.
@lc-soft has funded $2.00 to this issue.
- Submit pull request via IssueHunt to receive this reward.
- Want to contribute? Chip in to this issue via IssueHunt.
- Checkout the IssueHunt Issue Explorer to see more funded issues.
- Need help from developers? Add your repository on IssueHunt to raise funds.
@lc-soft win上 一些组合键输入乱码 例如:ctrl+v ctrl+n 等。 顺便附上 ctrl+v win32实现
static void OnKeyup(LCUI_Widget w, LCUI_WidgetEvent e, void *arg)
{
if (e->key.code == 229 || e->key.code == 86)
{
if (LCUIKeyboard_IsHit(17))
{
OpenClipboard(NULL);
HGLOBAL hGlobal = NULL;
hGlobal = GetClipboardData(CF_UNICODETEXT);
wchar_t *pGlobal = (wchar_t *)GlobalLock(hGlobal);
CloseClipboard();
TextEdit_InsertTextW(w, pGlobal);
}
}
}
@tangxg 这方式获取到的是纯文本吗?如果剪切板里有图片或富文本内容,那 GlobalLock()
获取到的是什么内容?
@lc-soft 目前只是在win10上测试过 获取(word,网页,VS编辑器,富文本编辑器等等)是纯文本,其他未测试过。 我看了下WIN32API 复制文件时应该可以取到 路径 测试发现内存地址都没得 是个NULL。
@lc-soft How would you expect the feature to be implemented? I assume the approach would be adding clipboard.c to platform (and equivalent linux_x11clipboard.c)
But then, how would the copy event be handled, It doesn't seem synchronous. The linked SDL approach seems to have a blocking while loop, however, I've seen another implementation, which can be found here. The explanation I've found was
As you ask another application to prepare the buffer, and that may take some time, the request is asynchronous: the owner prepares the buffer, saves it in a specified location (window property is used as a temporary storage) and notifies you with SelectionNotify event when it's done.
So I'm not certain you can just request XNextEvent
and guarantee that next event will be SelectionNotify
.
@WhoAteDaCake I expect the feature to be implemented as follows:
- Add file
src/clipboard.c
. - For Linux x11, it should use
LCUI_BindSysEvent()
to bind theSelectionNotify
event handler. - It should process the
SelectionNotify
event and store the clipboard content, only after calling the constructor function (for example:clipboard_create()
).
Wouldn't it have to know which widget is currently in focus, so it knows where to send the data once SelectionNotify
is retrieved.
Since SelectionNotify is triggered after XConvertSelection
is called, we should know where the event originated from.
Also, from what I understand we need to track the variables passed down to XConvertSelection
, so there would need to be some sort of state
My idea is to separate into something like:
Though I can image you'd want to avoid using things like LCUIMutex_Init
within platform specific code, so not really sure what's the best approach here :thinking:
Any thoughts
Wouldn't it have to know which widget is currently in focus, so it knows where to send the data once
SelectionNotify
is retrieved. Since SelectionNotify is triggered afterXConvertSelection
is called, we should know where the event originated from. Also, from what I understand we need to track the variables passed down toXConvertSelection
, so there would need to be some sort of stateMy idea is to separate into something like:
Though I can image you'd want to avoid using things like
LCUIMutex_Init
within platform specific code, so not really sure what's the best approach here thinkingAny thoughts
@WhoAteDaCake Why call the new ReigsterPaste()
function instead of the existing BindEvent()
function?
I expected the clipboard event processing flow to be as follows:
- Trigger
LCUI_PASTE
event - Call
LCUIWIdget_OnPaste()
event handler - Call
LCUIWidget_GetFocus()
to get focus widget (we assume TextEdit widget is the focus) - Trigger
LCUI_WEVENT_PASTE
event for focus widget - Call
TextEdit_OnPaste()
event handler - Call
Clipboard_GetText()
- Call
TextEdit_InsertTextW()
@lc-soft
The problem is at the step:
- Clipboard_GetText
Clipboard text can't be retrieved synchronously, we need to ask for it, by calling XConvertSelection
with some variables (which we need to store and reference when copying the clipboard after SelectionNotify is received). Now we have to wait until SelectionNotify
is received from the system and only then can we get the actual text.
I need to try and implement first I think, rather than guessing, but I think it we would wan't to let each platform handle the initial bit, so:
- linux_x11keyboard.c
- CTRL + V received, call (*SOME SORT OF INTERNAL EVENT)
- linux_x11clipboard.c
- Subscribe to (*SOME SORT OF INTERNAL EVENT)
- Call XConvertSelection, save the needed variables to file state (via static)
- return
- Subscribe to
SelectionNotify
OnSelectionNotify- Function called, store the text locally
- Trigger
LCUI_PASTE_READY
event
- Subscribe to (*SOME SORT OF INTERNAL EVENT)
- clipboard.c
- Subscribe to
LCUI_PASTE_READY
- On trigger save text locally and call
LCUI_PASTE
- On trigger save text locally and call
- Subscribe to
*SOME SORT OF INTERNAL EVENT - Not sure what's the best one to use here
Now we can continue with the steps you've mentioned.
I guess the biggest concern is how to communicate for the Widget -> System
flow and Widget -> System -> Platform
, since for Copy
event, I think we will need to store the copied text on the platform level (linux_x11clipboard.c) due to the SelectionRequest
event being called.
If we choose to do that, we could skip the last step I've mentioned and have clipboard.c
call platform specific implementation to retrieve the pasted text.
I hope I'm making sense, let me know if any of my assumptions are wrong, I'm still trying to wrap my head around the codebase :)
Clipboard text can't be retrieved synchronously, we need to ask for it
Yes, I know. My idea is to asynchronously process SelectionNotify
event and store clipboard data, and when the data is ready, trigger the LCUI_PASTE
event to tell TextEdit widget, This makes the call to 'Clipboard_GetText()' synchronous for TextEdit.
I need to try and implement first I think, rather than guessing, but I think it we would wan't to let each platform handle the initial bit, so: ...
I think LCUI_PASTE_READY
and LCUI_PASTE
should be merged.
I guess the biggest concern is how to communicate for the Widget -> System flow and Widget -> System -> Platform, since for Copy event, I think we will need to store the copied text on the platform level (linux_x11clipboard.c) due to the SelectionRequest event being called.
I agree.
@lc-soft Few questions
- would you expect the
LCUI_RegisterPaste
event to be called directly fromOnKeyboardMessage
or should it trigger a custom event? - If it is a direct call, how would I go about including it using the headers (I'm not a C expert)? Would I include linux_x11clipboard.h or go about it some other way? Thanks for your help!
@WhoAteDaCake
Sorry, there are some problems with my comments above, the idea described in my comment is only suitable for read clipboard content when CTRL+V is pressed, not for read clipboard content at any time.
I redesigned the usage of the Clipboard API. The example pseudo code is as follows:
typedef struct LCUI_TextEditRec_ {
// ...
LCUI_Clipboard clipboard;
};
void TextEdit_OnInit(LCUI_Widget w)
{
// ...
// Create a clipboard object for storing clipboard data
textedit->clipboard = LCUI_CreateClipboard();
// ...
}
void TextEdit_OnDestroy(LCUI_Widget w)
{
// ...
LCUI_DestroyClipboard(textedit->clipboard);
textedit->clipboard = NULL;
// ...
}
void TextEdit_OnPaste(LCUI_Widget w, LCUI_WidgetEvent e, void *arg)
{
const char *text;
const wchar_t *wtext;
LCUI_Clipboard cb = arg;
if (Clipboard_HasText(cb)) {
text = Clipboard_GetText(cb);
// Convert to unicode string
// ...
TextEdit_InsertTextW(w, wtext);
}
}
// In Linux x11, this function will be called when SelectionNotify event is received
void TextEdit_OnClipboardReady(LCUI_Clipboard clipboard, void *arg)
{
LCUI_Widget w = arg;
LCUI_WidgetEventRec e = { 0 };
LCUI_InitWidgetEvent(&e, "paste");
Widget_TriggerEvent(w, &e, clipboard);
}
void TextEdit_OnKeyDown(LCUI_Widget w, LCUI_WidgetEvent e, void *arg)
{
LCUI_Clipboard clipboard;
LCUI_TextEdit textedit;
// ...
if (is pressed Ctrl+V) {
// Asynchronously preparing clipboard data
// In linux x11, it will call XConvertSelection()
LCUI_UseClipboard(textedit->clipboard, TextEdit_OnClipboardReady, w);
}
}
Note:
- No
LCUI_PASTE
andLCUI_PASTE_READY
events - The clipboard does not need to care about keyboard events
@lc-soft
Wouldn't storing clipboard at widget, rather than system level cause problems? If we wan't to support CTRL+C feature, we'd need to support SelectionRequest
event (It would come if some other window asks for the the text we copied), meaning we need access to clipboard selected text from within linux_x11clipboard.c
.
I'd propose, we keep the clipboard
data as part of each system file linux_x11clipboard.c
, windows_clipboard.c
etc. Other than that, I think your suggestion is the right approach!
@WhoAteDaCake
Well, storing the clipboard in widgets would increase the cost and implementation complexity of the clipboard API, so I think we can choose to store the clipboard at the system level.
@lc-soft has rewarded $1.80 to @whoatedacake. See it on IssueHunt
- :moneybag: Total deposit: $2.00
- :tada: Repository reward(0%): $0.00
- :wrench: Service fee(10%): $0.20