1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2005 Novell, Inc.
23 // Jackson Harper (jackson@ximian.com)
30 using System.Collections;
31 using System.Runtime.InteropServices;
34 namespace System.Windows.Forms {
36 internal class X11Dnd {
38 private delegate void MimeConverter (IntPtr dsp,
39 DataObject data, ref XEvent xevent);
41 private class MimeHandler {
44 public IntPtr NonProtocol;
45 public MimeConverter Convert;
47 public MimeHandler (string name, MimeConverter converter)
54 private MimeHandler [] MimeHandlers = {
55 // new MimeHandler ("WCF_DIB"),
56 // new MimeHandler ("image/gif", new MimeConverter (ImageConverter)),
59 new MimeHandler ("text/rtf", new MimeConverter (RtfConverter)),
60 new MimeHandler ("text/richtext", new MimeConverter (RtfConverter)),
61 new MimeHandler ("text/plain", new MimeConverter (TextConverter)),
62 new MimeHandler ("text/html", new MimeConverter (HtmlConverter)),
63 new MimeHandler ("text/uri-list", new MimeConverter (UriListConverter)),
66 // This version seems to be the most common
67 private static readonly uint [] XdndVersion = new uint [] { 4 };
69 private IntPtr display;
72 private bool initialized;
74 private IntPtr XdndAware;
75 private IntPtr XdndSelection;
76 private IntPtr XdndEnter;
77 private IntPtr XdndLeave;
78 private IntPtr XdndPosition;
79 private IntPtr XdndDrop;
80 private IntPtr XdndFinished;
81 private IntPtr XdndStatus;
82 private IntPtr XdndTypeList;
83 private IntPtr XdndActionCopy;
84 private IntPtr XdndActionMove;
85 private IntPtr XdndActionLink;
86 private IntPtr XdndActionPrivate;
87 private IntPtr XdndActionList;
88 private IntPtr XdndActionDescription;
89 private IntPtr XdndActionAsk;
91 private int converts_pending;
92 private bool position_recieved;
93 private bool status_sent;
94 private IntPtr target;
95 private IntPtr source;
96 private IntPtr toplevel;
97 private DataObject data;
98 private Control control;
99 private int pos_x, pos_y;
100 private DragDropEffects allowed;
101 private DragEventArgs drag_event;
103 public X11Dnd (IntPtr display)
105 this.display = display;
108 public void SetAllowDrop (Hwnd hwnd, bool allow)
113 // if (hwnd.allow_drop == allow)
116 XChangeProperty (display, hwnd.whole_window, XdndAware,
117 (IntPtr) Atom.XA_ATOM, 32,
118 PropertyMode.Replace, XdndVersion, allow ? 1 : 0);
119 // hwnd.allow_drop = allow;
122 // return true if the event is handled here
123 public bool HandleClientMessage (ref XEvent xevent)
128 // most common so we check it first
129 if (xevent.ClientMessageEvent.message_type == XdndPosition)
130 return HandlePositionEvent (ref xevent);
131 if (xevent.ClientMessageEvent.message_type == XdndEnter)
132 return HandleEnterEvent (ref xevent);
133 if (xevent.ClientMessageEvent.message_type == XdndDrop)
134 return HandleDropEvent (ref xevent);
135 if (xevent.ClientMessageEvent.message_type == XdndLeave)
136 return HandleLeaveEvent (ref xevent);
141 public bool HandleSelectionNotifyEvent (ref XEvent xevent)
143 if (source != XGetSelectionOwner (display, XdndSelection))
146 MimeHandler handler = FindHandler ((IntPtr) xevent.SelectionEvent.target);
150 data = new DataObject ();
152 handler.Convert (display, data, ref xevent);
155 if (converts_pending <= 0 && position_recieved)
160 private void Reset ()
166 private void ResetSourceData ()
168 converts_pending = 0;
172 private void ResetTargetData ()
174 position_recieved = false;
178 private bool HandleEnterEvent (ref XEvent xevent)
182 source = xevent.ClientMessageEvent.ptr1;
183 toplevel = xevent.AnyEvent.window;
184 target = IntPtr.Zero;
186 ConvertData (ref xevent);
191 private bool HandlePositionEvent (ref XEvent xevent)
193 int x = (int) xevent.ClientMessageEvent.ptr3 >> 16;
194 int y = (int) xevent.ClientMessageEvent.ptr3 & 0xFFFF;
196 allowed = EffectFromAction (xevent.ClientMessageEvent.ptr5);
198 IntPtr parent, child, new_child;
199 parent = XplatUIX11.XRootWindow (display, 0);
203 new_child = IntPtr.Zero;
205 if (!XplatUIX11.XTranslateCoordinates (display,
207 out xd, out yd, out new_child))
209 if (new_child == IntPtr.Zero)
214 if (target != child) {
215 // We have moved into a new control
216 // or into a control for the first time
220 Hwnd hwnd = Hwnd.ObjectFromHandle (target);
221 Control c = Control.FromHandle (hwnd.client_window);
225 // if (!c.AllowDrop) {
231 position_recieved = true;
233 if (converts_pending > 0)
239 control.DndOver (drag_event);
245 private void Finish ()
247 if (control != null) {
248 if (drag_event == null) {
250 data = new DataObject ();
251 drag_event = new DragEventArgs (data,
253 allowed, DragDropEffects.None);
255 control.DndLeave (drag_event);
260 private bool HandleDropEvent (ref XEvent xevent)
262 Console.WriteLine ("DROPPING EVENT");
263 if (control != null && drag_event != null)
264 control.DndDrop (drag_event);
269 private bool HandleLeaveEvent (ref XEvent xevent)
271 if (control != null && drag_event != null)
272 control.DndLeave (drag_event);
277 private DragDropEffects EffectFromAction (IntPtr action)
279 DragDropEffects allowed = DragDropEffects.None;
280 if (action == XdndActionCopy)
281 allowed = DragDropEffects.Copy;
282 if (action == XdndActionMove)
283 allowed = DragDropEffects.Move;
284 if (action == XdndActionLink)
285 allowed = DragDropEffects.Link;
289 private IntPtr ActionFromEffect (DragDropEffects effect)
291 IntPtr action = IntPtr.Zero;
292 if (effect == DragDropEffects.Copy)
293 action = XdndActionCopy;
294 if (effect == DragDropEffects.Move)
295 action = XdndActionMove;
296 if (effect == DragDropEffects.Link)
297 action = XdndActionLink;
301 private bool ConvertData (ref XEvent xevent)
305 foreach (IntPtr atom in SourceSupportedList (ref xevent)) {
306 MimeHandler handler = FindHandler (atom);
309 XConvertSelection (display, XdndSelection, handler.Type,
310 handler.NonProtocol, toplevel, 0 /* CurrentTime */);
317 private MimeHandler FindHandler (IntPtr atom)
319 if (atom == IntPtr.Zero)
321 foreach (MimeHandler handler in MimeHandlers) {
322 if (handler.Type == atom)
328 private void SendStatus ()
330 DragDropEffects action = drag_event.Effect;
331 XEvent xevent = new XEvent ();
333 xevent.AnyEvent.type = XEventName.ClientMessage;
334 xevent.AnyEvent.display = display;
335 xevent.ClientMessageEvent.window = source;
336 xevent.ClientMessageEvent.message_type = XdndStatus;
337 xevent.ClientMessageEvent.format = 32;
338 xevent.ClientMessageEvent.ptr1 = toplevel;
339 if (drag_event.Effect != DragDropEffects.None)
340 xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;
342 xevent.ClientMessageEvent.ptr5 = ActionFromEffect (action);
343 XSendEvent (display, source, false, 0, ref xevent);
346 private void SendEnterStatus ()
348 drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
349 allowed, DragDropEffects.None);
350 control.DndEnter (drag_event);
352 XEvent xevent = new XEvent ();
354 xevent.AnyEvent.type = XEventName.ClientMessage;
355 xevent.AnyEvent.display = display;
356 xevent.ClientMessageEvent.window = source;
357 xevent.ClientMessageEvent.message_type = XdndStatus;
358 xevent.ClientMessageEvent.format = 32;
359 xevent.ClientMessageEvent.ptr1 = toplevel;
360 if (drag_event.Effect != DragDropEffects.None)
361 xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;
363 xevent.ClientMessageEvent.ptr5 = ActionFromEffect (drag_event.Effect);
364 XSendEvent (display, source, false, 0, ref xevent);
369 private void SendFinished ()
371 XEvent xevent = new XEvent ();
373 xevent.AnyEvent.type = XEventName.ClientMessage;
374 xevent.AnyEvent.display = display;
375 xevent.ClientMessageEvent.window = source;
376 xevent.ClientMessageEvent.message_type = XdndFinished;
377 xevent.ClientMessageEvent.format = 32;
378 xevent.ClientMessageEvent.ptr1 = toplevel;
380 XSendEvent (display, source, false, 0, ref xevent);
383 // There is a somewhat decent amount of overhead
384 // involved in setting up dnd so we do it lazily
385 // as a lot of applications do not even use it.
388 XdndAware = XInternAtom (display, "XdndAware", false);
389 XdndEnter = XInternAtom (display, "XdndEnter", false);
390 XdndLeave = XInternAtom (display, "XdndLeave", false);
391 XdndPosition = XInternAtom (display, "XdndPosition", false);
392 XdndStatus = XInternAtom (display, "XdndStatus", false);
393 XdndDrop = XInternAtom (display, "XdndDrop", false);
394 XdndSelection = XInternAtom (display, "XdndSelection", false);
395 XdndFinished = XInternAtom (display, "XdndFinished", false);
396 XdndTypeList = XInternAtom (display, "XdndTypeList", false);
397 XdndActionCopy = XInternAtom (display, "XdndActionCopy", false);
398 XdndActionMove = XInternAtom (display, "XdndActionMove", false);
399 XdndActionLink = XInternAtom (display, "XdndActionLink", false);
400 XdndActionPrivate = XInternAtom (display, "XdndActionPrivate", false);
401 XdndActionList = XInternAtom (display, "XdndActionList", false);
402 XdndActionDescription = XInternAtom (display, "XdndActionDescription", false);
403 XdndActionAsk = XInternAtom (display, "XdndActionAsk", false);
405 foreach (MimeHandler handler in MimeHandlers) {
406 handler.Type = XInternAtom (display, handler.Name, false);
407 handler.NonProtocol = XInternAtom (display,
408 String.Concat ("MWFNonP+", handler.Name), false);
414 private IntPtr [] SourceSupportedList (ref XEvent xevent)
418 if (((int) xevent.ClientMessageEvent.ptr2 & 0x1) == 0) {
419 res = new IntPtr [3];
420 res [0] = xevent.ClientMessageEvent.ptr3;
421 res [1] = xevent.ClientMessageEvent.ptr4;
422 res [2] = xevent.ClientMessageEvent.ptr5;
425 int format, count, remaining;
426 IntPtr data = IntPtr.Zero;
428 XGetWindowProperty (display, source, XdndTypeList,
429 0, 32, false, (IntPtr) Atom.XA_ATOM,
430 out type, out format, out count,
431 out remaining, out data);
433 res = new IntPtr [count];
434 for (int i = 0; i < count; i++) {
435 res [i] = (IntPtr) Marshal.ReadInt32 (data, i * sizeof (int));
442 private static void UriListConverter (IntPtr display, DataObject data,
445 string text = GetText (display, ref xevent, false);
449 // TODO: Do this in a loop instead of just splitting
450 ArrayList uri_list = new ArrayList ();
451 string [] lines = text.Split (new char [] { '\r', '\n' });
452 foreach (string line in lines) {
453 // # is a comment line (see RFC 2483)
454 if (line.StartsWith ("#"))
457 Uri uri = new Uri (line);
458 uri_list.Add (uri.LocalPath);
462 string [] l = (string []) uri_list.ToArray (typeof (string));
465 data.SetData (DataFormats.FileDrop, l);
466 data.SetData ("FileName", l [0]);
467 data.SetData ("FileNameW", l [0]);
470 private static void TextConverter (IntPtr display, DataObject data, ref XEvent xevent)
472 string text = GetText (display, ref xevent, false);
475 data.SetData (DataFormats.Text, text);
476 data.SetData (DataFormats.UnicodeText, text);
479 private static void HtmlConverter (IntPtr display, DataObject data, ref XEvent xevent)
481 string html = GetText (display, ref xevent, true);
484 data.SetData (DataFormats.Html, html);
487 private static void ImageConverter (IntPtr display, DataObject data, ref XEvent xevent)
491 private static void RtfConverter (IntPtr display, DataObject data, ref XEvent xevent)
495 private static string GetText (IntPtr display, ref XEvent xevent, bool unicode)
501 StringBuilder builder = new StringBuilder ();
505 IntPtr data = IntPtr.Zero;
507 if (0 != XGetWindowProperty (display,
508 xevent.AnyEvent.window,
509 (IntPtr) xevent.SelectionEvent.property,
511 (IntPtr) Atom.AnyPropertyType, out actual_type,
512 out actual_fmt, out nitems, out bytes_after,
519 builder.Append (Marshal.PtrToStringUni (data));
521 builder.Append (Marshal.PtrToStringAnsi (data));
525 } while (bytes_after > 0);
528 return builder.ToString ();
531 [DllImport ("libX11")]
532 private extern static string XGetAtomName (IntPtr display, IntPtr atom);
534 [DllImport ("libX11")]
535 private extern static IntPtr XInternAtom (IntPtr display, string atom_name, bool only_if_exists);
537 [DllImport ("libX11")]
538 private extern static int XChangeProperty (IntPtr display, IntPtr window, IntPtr property,
539 IntPtr format, int type, PropertyMode mode, uint [] atoms, int nelements);
541 [DllImport ("libX11")]
542 private extern static int XGetWindowProperty (IntPtr display, IntPtr window,
543 IntPtr atom, int long_offset, int long_length, bool delete,
544 IntPtr req_type, out IntPtr actual_type, out int actual_format,
545 out int nitems, out int bytes_after, out IntPtr prop);
547 [DllImport ("libX11")]
548 internal extern static int XSendEvent (IntPtr display, IntPtr window,
549 bool propagate, EventMask event_mask, ref XEvent send_event);
551 [DllImport ("libX11")]
552 internal extern static int XConvertSelection (IntPtr display, IntPtr selection,
553 IntPtr target, IntPtr property, IntPtr requestor, int time);
555 [DllImport ("libX11")]
556 internal extern static IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection);
558 [DllImport ("libX11")]
559 internal extern static int XFree(IntPtr data);