summaryrefslogtreecommitdiff
path: root/docs/dnd_internals.txt
blob: fc5afcecc01cf2cf03a9731761edfc0a6e4c89f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
This document describes some of the internals of the DND handling
code.

Organization
============

The DND code is split between a lowlevel part - gdkdnd.c and a
highlevel part - gtkdnd.c.  To put it simply, gdkdnd.c contain the
portions of DND code that are easiest to do in raw X, while gtkdnd.c
contains the portions of DND that are easiest to do with an event loop
and high level selection handling.

Except for a few details of selection handling, most of the
dependencies on the DND protocol are confined to gdkdnd.c.
There are two or three supported protocols - Motif DND,
Xdnd and a pseudo-protocol ROOTWIN, which is used for drops
on root windows that aren't really accepting drops.
gdkdnd.c divides into 4 pieces:

 1) Utility functions (finding client windows)
 2) Motif specific code (the biggest chunk)
 3) Xdnd specific code
 4) The public interfaces

The code in gtkdnd.c roughly consists of three parts  
  
 1) General utility functions
 2) Destination side code
 3) Source side code.

Both on the source and dest side, there is some division
between the low level layers and the default handlers,
though they are rather mixed in many cases.

Structures and Memory Management
================================

Information about source sites and drop sites is stored
in the structures GtkSourceSite and GtkDestSite.

Information about in-progress drags and drops is stored
in the structures GtkSourceInfo and GtkDestInfo.

The GtkSourceInfo structure is created when the drag
begins, and persists until the drag either completes
or times out. A pointer to it is stored in 
dataset-data for the GdkDragContext, however there
is no ownership. If the SourceInfo is destroyed
before the context, the field is simply cleared.

A GtkDestInfo is attached to each GdkDragContext
that is received for an incoming drag. In contrast
to the SourceInfo the DestInfo is "owned" by the
context, and when the context is destroyed, destroyed.

The GDK API
===========

It is expect that the GDK DND API will never be
used by anything other than the DND code in GTK+.

/* Drag and Drop */

GdkDragContext * gdk_drag_context_new        (void);

These create and refcount GdkDragContexts in a
straightforward manner.

/* Destination side */

void             gdk_drag_status        (GdkDragContext   *context,
				         GdkDragAction     action,
					 guint32           time);
void             gdk_drop_reply         (GdkDragContext   *context,
					 gboolean          ok,
					 guint32           time);
void             gdk_drop_finish        (GdkDragContext   *context,
					 gboolean          success,
					 guint32           time);
GdkAtom          gdk_drag_get_selection (GdkDragContext   *context);

/* Source side */

GdkDragContext * gdk_drag_begin      (GdkWindow      *window,
				      GList          *targets,
				      GdkDragAction   actions);
gboolean         gdk_drag_get_protocol (guint32          xid,
					GdkDragProtocol *protocol);
void             gdk_drag_find_window (GdkDragContext   *context,
				       GdkWindow       *drag_window,
			 	       gint             x_root,
				       gint             y_root,
				       GdkWindow      **dest_window,
				       GdkDragProtocol *protocol);
gboolean        gdk_drag_motion      (GdkDragContext *context,
				      GdkWindow      *dest_window,
				      GdkDragProtocol protocol,
				      gint            x_root, 
				      gint            y_root,
				      GdkDragAction   action,
				      guint32         time);
void            gdk_drag_drop        (GdkDragContext *context,
				      guint32         time);
void            gdk_drag_abort       (GdkDragContext *context,
				      guint32         time);

GdkAtom       gdk_drag_get_selection (GdkDragContext *context);

Retrieves the selection that will be used to communicate
the data for the drag context (valid on both source
and dest sides)

Cursors and window hierarchies
==============================

The DND code, when possible (and it isn't possible over
Motif window) uses a shaped window as a drag icon.
Because the cursor may fall inside this window during the
drag, we actually have to figure out which window
the cursor is in _ourselves_ so we can ignore the
drag icon properly. (Oh for OutputOnly windows!)

To avoid obscene amounts of server traffic (which are only
slightly observable locally, but would really kill a
session over a slow link), the code in GDK does
XGetWindowAttributes for every child of the root window at
the beginning of the drag, then selects with
SubstructureNotifyMask on the root window, so that
it can update this list.

It probably would be easier to just reread the entire
list when one of these events occurs, instead of 
incrementally updating, but updating the list in
sync was sort of fun code, so I did it that way ;-)

There is also a problem of trying to follow the
mouse cursor as well as possible. Currently, the
code uses PointerMotionHint, and an XQueryPointer
on MotionNotify events. This results in pretty
good syncing, but may result in somewhat poor
accuracy for drops. (Because the coordinates of
the drop are the coordinates when the server receives
the button press, which might actually be before
the XQueryPointer for the previous MotionNotify
event is done.)

Probably better is doing MotionNotify compression 
and discarding MotionNotify events when there
are more on the queue before the next ButtonPress/Release.

Proxying
========

A perhaps rather unusual feature of GTK's DND is proxying. A
dest site can be specified as a proxy drop site for another
window. This is most needed for the plug-socket code - the
socket needs to pass on drags to the plug since the original
source only sees toplevel windows. However, it can also be
used as a user visible proxy - i.e., dragging to buttons on
the taskbar.

Internally, when the outer drag enters a proxy dest site, a
new source drag is created, with SourceInfo and
GdkDragContext. From the GDK side, it looks much like a
normal source drag; on the GTK+ side, most of the code is
disjoint. The need to pass in a specific target window
is the reason why the GDK DND API splits
gdk_drag_find_window() and gdk_drag_motion().

For proxy drags, the GtkDestInfo and GtkSourceInfo for the
drag point at each other.

Because the abstraction of the drag protocol is at the GDK
level, a proxy drag from Motif to Xdnd or vice versa happens
pretty much automatically during the drag, though the
drop can get complicated. For Xdnd <-> Motif,
Motif <-> Xdnd, or Motif <-> Motif drags, it is necessary to 
for the Proxy to retrieve the data and pass it on to
the true destination, since either the selection names
differ or (Motif<->Motif), the proxy needs to know
about the XmDRAG_SUCCESS/FAILURE selection targets.

Further Reading:
================

Xdnd:

The spec is at:

 http://www.cco.caltech.edu/~jafl/xdnd/

Motif:

The Motif DND protocol is best described in the 
Hungry Programmers _Inside Lesstif_ book, available
from:

  http://www.igpm.rwth-aachen.de/~albrecht/hungry.html

Harald Albrecht and Mitch Miers have done a far
better job at documenting the DND protocol then
anything the OpenGroup has produced.



Owen Taylor
otaylor@redhat.com
Oct 18, 1998