Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uiDragDestination for text and file drop. #245

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

szanni
Copy link
Contributor

@szanni szanni commented Nov 2, 2023

Add text and file drop for all uiControls on all platforms.

This introduces new types:

  • uiDragDestination: Holds accepted content types and callbacks
  • uiDragContext: Context related to cursor position, supported drag operations and a reference to the dragged data itself
  • uiDragData: the actual dragged data

You can then register the drag destination with a uiControl:
uiControlRegisterDragDestination().

The Drag and drop documentation included should give you a good overview on the basic workings.

I currently only test uiLabel as a drag destination. All other controls should work but are untested by me (apart from uiWindow).
Any suggestions on how to go about testing all controls? Do a drag destination test for each control within the qa tester? Most controls are not tested within yet.

I would personally like to merge the other qa tests first to make sure I didn't break anything while refactoring the darwin code.

I believe this should also close/supersede old PR #378 as we already have uiWindowOnContentSizeChanged() , uiWindowOnFocusChanged(), uiWindowOnClosing() implemented upstream and file drop support will be provided by this PR.

Fixes #217 - at least the initial request. Custom mime types are not included in this PR, but this should be possible by adding a uiDragDestinationSetAcceptedStringTypes() or similar without interfering with the predefined types API, that most people will probably use.
QT for example provides a conversion utility to convert from custom mimes to built in types. To me that honestly seems somewhat brittle. My thought are:
Provide a predefined types API via uiDragDestinationAcceptedTypes.
Provide a mime type API via uiDragDestinationAcceptedStringTypes and filter out the overlapping predefined types.

Reviews welcome. This is a massive change set.

New types:
uiDragType
uiDragOperation

New structs:
uiDragDestination
uiDragContext
uiDragData

WARNING:
Currently only works cross platform for uiWindow.
- uiBox
- uiButton
- uiCheckbox
- uiColorButton
- uiCombobox
- uiDateTimePicker
- uiEditableCombobox
- uiFontButton
- uiForm
- uiGrid
- uiMultilineEntry
- uiSlider
- uiSpinbox
- uiTable

MISSING:
- uiEntry
- uiGroup
- uiLabel
- uiProgressBar
- uiRadioButtons
- uiSeperator
- uiTab
Copy link
Contributor Author

@szanni szanni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some spelling and wording fixes.
I'll commit and squash these later in one go once closer to merging.

ui.h Outdated Show resolved Hide resolved
ui.h Outdated Show resolved Hide resolved
*
* @memberof uiDragContext
*/
_UI_EXTERN void uiDragContextPosition(uiDragContext *dc, int *x, int *y);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly rename this to uiDragContextCursorPosition?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed msvc requires /execution-charset:utf-8 as a compiler option to use utf-8 literals.

libui_qa_c_args = []

if libui_OS == 'windows'
	if libui_MSVC
		libui_qa_c_args += ['/execution-charset:utf-8']
	endif
endif

executable('qa', libui_qa_sources,
	dependencies: libui_binary_deps,
	link_with: libui_libui,
	c_args: libui_qa_c_args,
	gui_app: false,
	install: false)

Or msvc replaces utf-8 characters with "?" (at least on Windows 10 with Visual Studio 2022.)
utf8_literal

Copy link
Contributor Author

@szanni szanni Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that. I had not encountered this. Maybe because of Windows 7 or using only mingw compiled binaries for testing.

Edit: This should probably be it's own bug? Looks like Unicode string literals are broken on Windows 10/MSVC?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the cause is the system locale. (Control Panel > Clock and Region > Region > Administrative > Change system locale)
Windows still uses old charsets for localization (even for the English locales), and MSVC uses the locale to parse files.
You can change the locale to UTF-8 but it's still beta.

@matyalatte
Copy link
Contributor

matyalatte commented Feb 6, 2024

I confirmed that the qa tests work fine on Ubuntu 20.04 and Windows 10.
But I got segfault on macOS 10.15 when I dragged a file over a multiline entry.

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libui.A.dylib                 	0x0000000103cefdcf -[intrinsicSizeTextView draggingEntered:] + 63 (multilineentry.m:25)
1   com.apple.AppKit              	0x00007fff33ba2ecd -[NSDragDestination _draggingEntered] + 36
2   com.apple.AppKit              	0x00007fff33954c4c NSCoreDragTrackingProc + 1590
3   com.apple.HIServices          	0x00007fff34622c63 DoTrackingMessage + 367
4   com.apple.HIServices          	0x00007fff346280c6 CoreDragMessageHandler + 729
5   com.apple.CoreFoundation      	0x00007fff3641cdd0 __CFMessagePortPerform + 621
6   com.apple.CoreFoundation      	0x00007fff36375ea3 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
7   com.apple.CoreFoundation      	0x00007fff36375df2 __CFRunLoopDoSource1 + 541
8   com.apple.CoreFoundation      	0x00007fff3637490c __CFRunLoopRun + 2284
9   com.apple.CoreFoundation      	0x00007fff363739c3 CFRunLoopRunSpecific + 466
10  com.apple.HIToolbox           	0x00007fff34f90abd RunCurrentEventLoopInMode + 292
11  com.apple.HIToolbox           	0x00007fff34f907d5 ReceiveNextEventCommon + 584
12  com.apple.HIToolbox           	0x00007fff34f90579 _BlockUntilNextEventMatchingListInModeWithFilter + 64
13  com.apple.AppKit              	0x00007fff335d8829 _DPSNextEvent + 883
14  com.apple.AppKit              	0x00007fff335d7070 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1352
15  com.apple.AppKit              	0x00007fff335c8d7e -[NSApplication run] + 658
16  libui.A.dylib                 	0x0000000103cedf5c uiMain + 44 (main.m:167)
17  qa                            	0x0000000103cacd1a main + 570 (main.c:215)
18  libdyld.dylib                 	0x00007fff7028acc9 start + 1

(edit)
confirmed that c->dragDest is NULL when draggingEntered is called.

@matyalatte
Copy link
Contributor

These are nits but codespell found more typos.

examples/drag-drop/main.c:98: manger ==> manager
test/qa/dragdestination.c:93: manger ==> manager

And git diff found many files that have two blank lines at EOF.

$ git -c 'core.whitespace=trailing-space,space-before-tab,indent-with-non-tab,tabwidth=2' --no-pager diff --check f0184e10dcdf17e9aea736d2247fee41efb381a3
common/dragdestination.c:77: new blank line at EOF.
darwin/control.m:115: new blank line at EOF.
darwin/dragcontext.m:81: new blank line at EOF.
darwin/dragdata.m:20: new blank line at EOF.
darwin/dragdestination.m:17: new blank line at EOF.
examples/drag-drop/main.c:239: new blank line at EOF.
test/qa/dragdestination.c:410: new blank line at EOF.
unix/control.c:207: new blank line at EOF.
unix/dragcontext.c:90: new blank line at EOF.
unix/dragcontext.h:23: new blank line at EOF.
unix/dragdata.c:20: new blank line at EOF.
windows/control.cpp:162: new blank line at EOF.
windows/dragcontext.cpp:132: new blank line at EOF.
windows/dragcontext.hpp:13: new blank line at EOF.
windows/dragdata.cpp:20: new blank line at EOF.
windows/droptarget.cpp:130: new blank line at EOF.

@matyalatte
Copy link
Contributor

matyalatte commented Feb 25, 2024

I noticed the qa test crashed at 7.Drag Context Drag Data > Step 1 on Ubuntu 22.04.
I mean, it works on Ubuntu 20.04, but does not work on Ubuntu 22.04.

Here is the backtrace.

Thread 1 "qa" received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:74
74	../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.
#0  __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:74
#1  0x00007ffff7829278 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#2  0x00007ffff7fa57eb in uiMultilineEntryAppend
    (e=0x555555716700, text=0x1 <error: Cannot access memory at address 0x1>)
    at ../unix/multilineentry.c:59
#3  0x0000555555558204 in updateDragData (dd=0x5555556024a0, dc=0x7fffffffd8b0)
    at ../test/qa/dragdestination.c:39
#4  0x0000555555558891 in onEnterData
    (dd=0x5555556024a0, dc=0x7fffffffd8b0, senderData=0x0)
    at ../test/qa/dragdestination.c:326
#5  0x00007ffff7f9c047 in onDragMotion
    (widget=0x555555a99720, context=0x55555560d020, x=89, y=48, time=2303984, userdata=0x5555555b4c00) at ../unix/control.c:41
#6  0x00007ffff7905c96 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#7  0x00007ffff7246700 in g_signal_emit_valist ()
    at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#8  0x00007ffff7246a8e in g_signal_emit_by_name ()
    at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#9  0x00007ffff78e937a in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0

(edit)
You may need to repeat the step 1 to reproduce the situation. Sometimes it works fine.

if (types & accepted & uiDragTypeText) {
data = uiDragContextDragData(dc, uiDragTypeText);
if (data != NULL) {
uiMultilineEntryAppend(dragData, "text:\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed that data->type can be uiDragTypeURIs here on Ubuntu 22.04. I think it's related to the crash.

unix/control.c Outdated
d->data.URIs.URIs = uiprivAlloc(d->data.URIs.numURIs * sizeof(*d->data.URIs.URIs), "uiDragData->data.URIs.URIs");

for (i = 0; uris[i] != NULL; ++i) {
d->data.URIs.URIs[i] = g_filename_from_uri(uris[i], NULL, NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also noticed I can get another kind of errors at 7.Drag Context Drag Data > Step 1 on Ubuntu 22.04.

(qa:7735): Gtk-CRITICAL **: 12:35:14.684: gtk_text_buffer_insert: assertion 'text != NULL' failed
Thread 1 "qa" received signal SIGSEGV, Segmentation fault.
0x00007ffff7dec47e in __GI___libc_free (mem=0x20) at ./malloc/malloc.c:3368
3368	./malloc/malloc.c: No such file or directory.
#0  0x00007ffff7dec47e in __GI___libc_free (mem=0x20)
    at ./malloc/malloc.c:3368
#1  0x00007ffff7f9f020 in uiFreeDragData (d=0x555555a36dd0)
    at ../unix/dragdata.c:13
#2  0x00005555555582c9 in updateDragData
    (dd=0x555555ada470, dc=0x7fffffffd8b0)
    at ../test/qa/dragdestination.c:52
#3  0x0000555555558880 in onEnterData
    (dd=0x555555ada470, dc=0x7fffffffd8b0, senderData=0x0)
    at ../test/qa/dragdestination.c:324
#4  0x00007ffff7f9c047 in onDragMotion
    (widget=0x55555563ea60, context=0x55555560d020, x=98, y=50, time=10454598, userdata=0x555555794350) at ../unix/control.c:41
#5  0x00007ffff7905c96 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#6  0x00007ffff7246700 in g_signal_emit_valist ()
    at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#7  0x00007ffff7246a8e in g_signal_emit_by_name ()
    at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#8  0x00007ffff78e937a in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#9  0x00007ffff77709fb in gtk_main_do_event ()
    at /lib/x86_64-linux-gnu/libgtk-3.so.0

So, d->data.URIs.URIs[i] can be NULL here.
Then, the qa test crashes at uiFreeDragData.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that.
No idea how you managed to trigger that, but yes, the docs for g_filename_from_uri docs indeed state that they will return NULL on error.

I believe it should be fixed in 930e1da.

priv->requestedType = type;
gtk_drag_get_data(dc->widget, dc->context, atom, dc->time);
while (!priv->dataReceived)
if (!uiMainStep(1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed that this uiMainStep can fire other drag-drop events.
I think that's the cause of all crashes on Ubuntu22.04.

I added printf functions to trace the function calls.

[onEnterData] fired
[updateDragData] fired
[uiDragContextDragData] Requested type: 1
[uiMainStep]
[onDragDataReceived] info: 1
[uiDragContextDragData] Received type: 1 (Requested type: 1)
[uiDragContextDragData] Requested type: 2
[uiMainStep]
[onMoveData] fired
[updateDragData] fired
[uiDragContextDragData] Requested type: 1
[uiMainStep]
[onMoveData] fired
[onMoveData] finished
[uiMainStep]
[onMoveData] fired
[onMoveData] finished
[uiMainStep]
[onMoveData] fired
[onMoveData] finished
[uiMainStep]
[onDragDataReceived] info: 1
[uiDragContextDragData] Received type: 1 (Requested type: 1)
[uiDragContextDragData] Requested type: 2
[uiMainStep]
[onDragDataReceived] info: 2
[uiDragContextDragData] Received type: 2 (Requested type: 2)
[updateDragData] finished
[onMoveData] finished
[uiDragContextDragData] Received type: 2 (Requested type: 2)
(qa:2764): Gtk-CRITICAL **: 05:34:23.269: gtk_text_buffer_insert: assertion 'text != NULL' failed
Segmentation fault (core dumped)

They showed that onMoveData was fired while requesting URIs in onEnterData.
Then, uiDragContextDragData returned broken priv->data for onEnterData.

Copy link
Contributor Author

@szanni szanni Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... maybe a synchronous API is not possible after all? Not sure.
Maybe just adding another check within !priv->dragEnter would do the trick to block out all other processing? Seems very brittle though.

It does make sense that uiMainStep() would process all kinds of events... including mouse moves.
Can you reproduce this bug with the new patches? Because it shouldn't crash IMO, even if the order is all garbled.

And attach your gtk version as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you reproduce this bug with the new patches?

I got the same crash as the first one.
The problem is uiDragContextDragData can return the same pointer to both onEnterData and onMoveData.
In my case, uiDragContextDragData requests uiDragTypeURIs in onMoveData while requesting uiDragTypeText in onEnterData.
After returning URIs to onMoveData, it also returns the same URIs to onEnterData even if it requested uiDragTypeText.
Then, it crashes when using data->data.text.

And attach your gtk version as well?

3.24.33-1ubuntu2 on Ubuntu 22.04.

I think it also requires low-end machines to reproduce the situation.
I got segfault with 2GB RAM and one CPU core but it worked fine with 4GB RAM and dual core.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the condensed analysis. I am unsure about how to fix this.
We would need to essentially cache all move events (and possibly leave & drop) when still waiting for our data. Otherwise we might end up firing move or other events while the enter one has not even completed yet.

Simply blocking all further event processing while waiting for data might break the enter > move > leave/drop chain otherwise?

I'm almost tempted to implement XDnD myself. The API looks a lot saner than what Gtk offers.

It is strange that this seems to happen under memory pressure only. Gtk is very brittle, so I'm sadly not surprised. Thanks for the info though, maybe I can reproduce the issue with a 2GB RAM VM.

@szanni
Copy link
Contributor Author

szanni commented Mar 27, 2024

Thank you so much for testing! I will need to go through the crashes one by one. Gtk is such a pain...

These are nits but codespell found more typos.

examples/drag-drop/main.c:98: manger ==> manager
test/qa/dragdestination.c:93: manger ==> manager

Fixed, thanks.

And git diff found many files that have two blank lines at EOF.

Yes, as required by the C99 standard. Apparently C++11 dropped that requirement and I am unsure what Objective-C has to say about that.

@matyalatte
Copy link
Contributor

matyalatte commented Mar 27, 2024

Yes, as required by the C99 standard. Apparently C++11 dropped that requirement and I am unsure what Objective-C has to say about that.

I mean, they have more blank lines, not only the newlines that C requires.

I think your code editor hides the newlines.
You can use vi to see the hidden newlines as $.

vi unix/control.c
:set list
:$

You can see two $ symbols at EOF.
unix_control_vim

And it looks like this on VScode.
unix_control

@szanni
Copy link
Contributor Author

szanni commented Mar 27, 2024

I think your code editor hides the newlines. You can use vi to see the hidden newlines as $.

Interesting. Learnt something new today. Vim apparently adds a hidden newline to all files by default. Looking at a hex dump does indeed confirm that. Seems silly to not display it. I'll fix that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Enhancement] OnFilesDropped Event
2 participants