CPF/CPF.Linux/DataObject.cs

967 lines
36 KiB
C#
Raw Permalink Normal View History

2023-11-21 23:05:03 +08:00
using System;
using System.Collections.Generic;
using System.Text;
using CPF.Input;
using System.Linq;
using System.Runtime.InteropServices;
using CPF.Drawing;
namespace CPF.Linux
{
class DataObject : IDataObject
{
public static IntPtr textAtom = XLib.XInternAtom(LinuxPlatform.Platform.Display, "text/plain", false);
public static IntPtr textUtf8Atom = XLib.XInternAtom(LinuxPlatform.Platform.Display, "text/plain;charset=utf-8", false);
public static IntPtr htmlAtom = XLib.XInternAtom(LinuxPlatform.Platform.Display, "text/html", false);
public static IntPtr fileNamesAtom = XLib.XInternAtom(LinuxPlatform.Platform.Display, "text/uri-list", false);
public static List<(IntPtr, IntPtr, DataFormat)> atomNonProtocols = new List<(IntPtr, IntPtr, DataFormat)>();
static DataObject()
{
var atoms = LinuxPlatform.Platform.Info.Atoms;
//atomNonProtocols.Add((textAtom, XLib.XInternAtom(LinuxPlatform.Platform.Display,
// String.Concat("MWFNonP+", "text/plain"), false), DataFormat.Text));
//atomNonProtocols.Add((htmlAtom, XLib.XInternAtom(LinuxPlatform.Platform.Display,
// String.Concat("MWFNonP+", "text/html"), false), DataFormat.Html));
//atomNonProtocols.Add((fileNamesAtom, XLib.XInternAtom(LinuxPlatform.Platform.Display,
// String.Concat("MWFNonP+", "text/uri-list"), false), DataFormat.FileNames));
atomNonProtocols.Add((atoms.UTF8_STRING, atoms.UTF8_STRING, DataFormat.Text));
atomNonProtocols.Add((atoms.COMPOUND_TEXT, atoms.COMPOUND_TEXT, DataFormat.Text));
atomNonProtocols.Add((atoms.XA_STRING, atoms.XA_STRING, DataFormat.Text));
atomNonProtocols.Add((atoms.STRING, atoms.STRING, DataFormat.Text));
atomNonProtocols.Add((atoms.TEXT, atoms.TEXT, DataFormat.Text));
}
string text;
string html;
List<string> fileNames;
X11Info x11Info;
public DataObject()
{
x11Info = LinuxPlatform.Platform.Info;
}
public bool Contains(DataFormat dataFormat)
{
switch (dataFormat)
{
case DataFormat.Text:
return text != null;
case DataFormat.Html:
return html != null;
case DataFormat.FileNames:
return fileNames != null;
default:
return false;
}
}
public object GetData(DataFormat dataFormat)
{
switch (dataFormat)
{
case DataFormat.Text:
return text;
case DataFormat.Html:
return html;
case DataFormat.FileNames:
return fileNames;
default:
return null;
}
}
public DragDropEffects EffectFromAction(IntPtr action)
{
if (action == x11Info.Atoms.XdndActionCopy)
return DragDropEffects.Copy;
else if (action == x11Info.Atoms.XdndActionMove)
return DragDropEffects.Move;
if (action == x11Info.Atoms.XdndActionLink)
return DragDropEffects.Link;
return DragDropEffects.None;
}
private IntPtr ActionFromEffect(DragDropEffects effect)
{
IntPtr action = IntPtr.Zero;
// We can't OR together actions on XDND so sadly the primary
// is the only one shown here
if ((effect & DragDropEffects.Copy) != 0)
action = x11Info.Atoms.XdndActionCopy;
else if ((effect & DragDropEffects.Move) != 0)
action = x11Info.Atoms.XdndActionMove;
else if ((effect & DragDropEffects.Link) != 0)
action = x11Info.Atoms.XdndActionLink;
return action;
}
//private IntPtr target;
//private IntPtr source;
//private IntPtr toplevel;
IntPtr[] SupportedTypes;
Point point;
CancelHandle cancelHandle;
IntPtr LastWindow;
IntPtr TargetWindow;
IntPtr SourceWindow;
DragState State;
IntPtr Action;
public DragDropEffects AllowedEffects;
//private bool dropped = false;
(DataFormat, object)[] data;
public DragDropEffects StartDrag(DragDropEffects allowedEffects, params (DataFormat, object)[] data)
{
if (X11Window.mouseDownWindow == IntPtr.Zero)
{
throw new Exception("必须是鼠标按下的时候调用");
}
SourceWindow = X11Window.mouseDownWindow;
State = DragState.Beginning;
//MouseState = XplatUIX11.MouseState;
AllowedEffects = allowedEffects;
Action = ActionFromEffect(allowedEffects);
this.data = data;
var suc = XLib.XSetSelectionOwner(LinuxPlatform.Platform.Display, x11Info.Atoms.XdndSelection, X11Window.mouseDownWindow, IntPtr.Zero);
List<IntPtr> types = new List<IntPtr>();
foreach (var item in data)
{
switch (item.Item1)
{
case DataFormat.Text:
types.Add(textAtom);
types.Add(x11Info.Atoms.UTF8_STRING);
types.Add(x11Info.Atoms.STRING);
types.Add(x11Info.Atoms.TEXT);
types.Add(x11Info.Atoms.COMPOUND_TEXT);
types.Add(textUtf8Atom);
break;
case DataFormat.Html:
types.Add(htmlAtom);
break;
case DataFormat.FileNames:
types.Add(fileNamesAtom);
break;
default:
break;
}
}
SupportedTypes = types.ToArray();
if (suc == 0)
{
Console.Error.WriteLine("Could not take ownership of XdndSelection aborting drag.");
//drag_data.Reset();
return DragDropEffects.None;
}
//source = toplevel = target = X11Window.mouseDownWindow;
State = DragState.Dragging;
//dropped = false;
TargetWindow = X11Window.mouseDownWindow;
SendEnter(X11Window.mouseDownWindow, X11Window.mouseDownWindow, SupportedTypes);
if (cancelHandle != null)
{
cancelHandle.Cancel = true;
}
cancelHandle = new CancelHandle();
LinuxPlatform.Platform.RunMainLoop(cancelHandle, OnEvent);
//if (!dropped)
return EffectFromAction(Action);
}
bool WillAccept;
// This version seems to be the most common
private static readonly IntPtr[] XdndVersion = new IntPtr[] { new IntPtr(4) };
private unsafe void SendEnter(IntPtr handle, IntPtr from, IntPtr[] supported)
{
XEvent xevent = new XEvent();
xevent.AnyEvent.type = XEventName.ClientMessage;
xevent.AnyEvent.display = LinuxPlatform.Platform.Display;
xevent.ClientMessageEvent.window = handle;
xevent.ClientMessageEvent.message_type = x11Info.Atoms.XdndEnter;
xevent.ClientMessageEvent.format = 32;
xevent.ClientMessageEvent.ptr1 = from;
xevent.ClientMessageEvent.ptr5 = Action;
// (int) xevent.ClientMessageEvent.ptr2 & 0x1)
// int ptr2 = 0x1;
// xevent.ClientMessageEvent.ptr2 = (IntPtr) ptr2;
// (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24)
//xevent.ClientMessageEvent.ptr2 = (IntPtr)((long)XdndVersion[0] << 24);
xevent.ClientMessageEvent.ptr2 = (IntPtr)67108865;
//if (supported.Length > 0)
// xevent.ClientMessageEvent.ptr3 = supported[0];
//if (supported.Length > 1)
// xevent.ClientMessageEvent.ptr4 = supported[1];
//if (supported.Length > 2)
// xevent.ClientMessageEvent.ptr5 = supported[2];
//var list = supported.Select(a => (int)a).ToArray();
//fixed (void* data = list)
//{
XLib.XChangeProperty(x11Info.Display, from, x11Info.Atoms.XdndTypeList, (IntPtr)Atom.XA_ATOM, 32, PropertyMode.Replace, supported, supported.Length);
//}
Console.WriteLine("SendEnter" + handle);
XLib.XSendEvent(LinuxPlatform.Platform.Display, handle, false, IntPtr.Zero, ref xevent);
}
bool OnEvent(ref XEvent xEvent)
{
//Console.WriteLine(xEvent.type);
switch (xEvent.type)
{
case XEventName.ButtonRelease:
SetResult();
return true;
case XEventName.MotionNotify:
point = new Point(xEvent.MotionEvent.x, xEvent.MotionEvent.y);
HandleMouseOver();
return true;
case XEventName.GenericEvent:
unsafe
{
fixed (void* data = &xEvent.GenericEventCookie)
{
XLib.XGetEventData(LinuxPlatform.Platform.Info.Display, data);
try
{
if (LinuxPlatform.Platform.Info.XInputOpcode ==
xEvent.GenericEventCookie.extension)
{
var xev = (XIEvent*)xEvent.GenericEventCookie.data;
if (LinuxPlatform.Platform.windows.TryGetValue(((XIDeviceEvent*)xev)->EventWindow, out var window))
{
if (xev->evtype == XiEventType.XI_ButtonRelease)
{
SetResult();
}
else if (xev->evtype == XiEventType.XI_Motion)
{
var ev = (XIDeviceEvent*)xev;
point = new Point((float)ev->event_x, (float)ev->event_y);
HandleMouseOver();
}
}
}
}
finally
{
if (xEvent.GenericEventCookie.data != null)
XLib.XFreeEventData(LinuxPlatform.Platform.Info.Display, data);
}
}
return true;
}
case XEventName.ClientMessage:
if (xEvent.ClientMessageEvent.message_type == x11Info.Atoms.XdndStatus)
{
WillAccept = ((int)xEvent.ClientMessageEvent.ptr2 & 0x1) != 0;
GiveFeedback(xEvent.ClientMessageEvent.ptr5);
//Console.WriteLine("WillAccept" + WillAccept);
return true;
}
else if (xEvent.ClientMessageEvent.message_type == x11Info.Atoms.XdndFinished)
{
cancelHandle.Cancel = true;
return true;
}
break;
case XEventName.SelectionRequest:
if (xEvent.SelectionRequestEvent.selection != x11Info.Atoms.XdndSelection)
break;
//Console.WriteLine("requestor:" + xEvent.SelectionRequestEvent.requestor);
//foreach (var item in data)
//{
var type = xEvent.SelectionRequestEvent.target;
var item = data.FirstOrDefault();
if (type == textAtom || type == textUtf8Atom || type == x11Info.Atoms.STRING || type == x11Info.Atoms.TEXT)
{
item = data.FirstOrDefault(a => a.Item1 == DataFormat.Text);
}
else if (type == htmlAtom)
{
item = data.FirstOrDefault(a => a.Item1 == DataFormat.Html);
}
else if (type == fileNamesAtom)
{
item = data.FirstOrDefault(a => a.Item1 == DataFormat.FileNames);
}
if (item.Item2 != null)
{
switch (item.Item1)
{
case DataFormat.Text:
SetTextData(item.Item2, ref xEvent);
break;
case DataFormat.Html:
SetHtmlData(item.Item2, ref xEvent);
break;
case DataFormat.FileNames:
SetFileNameData(item.Item2, ref xEvent);
break;
}
//}
XLib.XFlush(x11Info.Display);
}
return true;
}
return false;
}
private void SetResult()
{
if (State == DragState.Beginning)
{
//state = State.Accepting;
}
else if (State != DragState.None)
{
if (WillAccept)
{
SendDrop(TargetWindow, SourceWindow, IntPtr.Zero);
SendLeave(TargetWindow, SourceWindow);
XLib.XChangeActivePointerGrab(x11Info.Display,
EventMask.ButtonMotionMask |
EventMask.PointerMotionMask |
EventMask.ButtonPressMask |
EventMask.ButtonReleaseMask,
(IntPtr)((Cursor)Cursors.Arrow).PlatformCursor, IntPtr.Zero);
//if (QueryContinue(false, DragAction.Drop))
// return;
}
else
{
cancelHandle.Cancel = true;
SendLeave(TargetWindow, SourceWindow);
//if (QueryContinue(false, DragAction.Cancel))
// return;
// fallback if no movement was detected, as .net does.
//if (motion_poll == -1)
// DefaultEnterLeave(drag_data.Data);
}
State = DragState.None;
// WE can't reset the drag data yet as it is still
// most likely going to be used by the SelectionRequest
// handlers
}
}
DataFormat dataFormat;
DragDropEffects dragDropEffects;
public DragDropEffects DropEffects
{
get { return dragDropEffects; }
}
public void SetDragDropEffects(DragDropEffects dragDropEffects)
{
if (dragDropEffects != this.dragDropEffects)
{
SendStatus(TargetWindow, dragDropEffects);//发送消息,确定可以接受数据
}
this.dragDropEffects = dragDropEffects;
}
Threading.DispatcherTimer timer;
public void StopTimer()
{
if (timer != null)
{
timer.Stop();
timer.Dispose();
timer = null;
}
if (cancelHandle != null)
{
cancelHandle.Cancel = true;
cancelHandle = null;
}
}
public void DragEnter(ref XEvent xEvent, EventHandler eventHandler)
{
dataFormat = DataFormat.Unknown;
text = null;
html = null;
fileNames = null;
TargetWindow = xEvent.AnyEvent.window;
SourceWindow = xEvent.ClientMessageEvent.ptr1;
SendStatus(SourceWindow, DropEffects);//发送消息,确定可以接受数据
//确认可以接受数据的格式
foreach (IntPtr atom in SourceSupportedList(ref xEvent))
{
//Console.WriteLine("格式:" + XLib.GetAtomName(x11Info.Display, atom));
var f = atomNonProtocols.FirstOrDefault(a => a.Item1 == atom);
Console.WriteLine(XLib.GetAtomName(LinuxPlatform.Platform.Display, atom));
if (f.Item1 == IntPtr.Zero || dataFormat.HasFlag(f.Item3))
{
continue;
}
dataFormat |= f.Item3;
XLib.XConvertSelection(x11Info.Display, x11Info.Atoms.XdndSelection, atom,
f.Item2, TargetWindow, IntPtr.Zero /* CurrentTime */);
}
if (cancelHandle != null)
{
cancelHandle.Cancel = true;
}
cancelHandle = new CancelHandle();
LinuxPlatform.Platform.RunMainLoop(cancelHandle, OnAcceptEvent);
if (cancelHandle != null)
{
if (timer == null)
{
timer = new Threading.DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
timer.Tick += eventHandler;
}
timer.Start();
}
}
public bool OnAcceptEvent(ref XEvent xEvent)
{
if (xEvent.type == XEventName.SelectionNotify)
{
var df = dataFormat;
while (true)
{
if (df == DataFormat.Unknown)
{
break;
}
if (df.HasFlag(DataFormat.Text))
{
df ^= DataFormat.Text;
text = GetText(ref xEvent);
}
if (df.HasFlag(DataFormat.Html))
{
df ^= DataFormat.Html;
html = GetText(ref xEvent);
}
if (df.HasFlag(DataFormat.FileNames))
{
if (text == null)
{
text = GetText(ref xEvent);
}
df ^= DataFormat.FileNames;
List<string> uri_list = new List<string>();
string[] lines = text.Split(new char[] { '\r', '\n' });
foreach (string line in lines)
{
// # is a comment line (see RFC 2483)
if (line.StartsWith("#"))
continue;
try
{
Uri uri = new Uri(line);
uri_list.Add(uri.LocalPath);
}
catch { }
}
fileNames = uri_list;
//string[] l = (string[])uri_list.ToArray(typeof(string));
}
}
if (cancelHandle != null)
{
cancelHandle.Cancel = true;
}
return true;
}
return false;
}
public unsafe string GetText(ref XEvent xevent)
{
int nread = 0;
IntPtr nitems;
IntPtr bytes_after;
StringBuilder builder = new StringBuilder();
do
{
IntPtr actual_type;
int actual_fmt;
IntPtr data = IntPtr.Zero;
if (0 != XLib.XGetWindowProperty(x11Info.Display,
xevent.AnyEvent.window,
(IntPtr)xevent.SelectionEvent.property,
IntPtr.Zero, new IntPtr(0xffffff), false,
(IntPtr)Atom.AnyPropertyType, out actual_type,
out actual_fmt, out nitems, out bytes_after,
out data))
{
XLib.XFree(data);
break;
}
//if (unicode)
// builder.Append(Marshal.PtrToStringUni(data));
//else
// builder.Append(Marshal.PtrToStringAnsi(data));
Encoding textEnc = GetStringEncoding(xevent.SelectionEvent.property);
if (textEnc != null)
{
var text = textEnc.GetString((byte*)data.ToPointer(), nitems.ToInt32());
builder.Append(text);
}
else
{
builder.Append(Marshal.PtrToStringAnsi(data));
}
nread += nitems.ToInt32();
XLib.XFree(data);
} while (bytes_after.ToInt32() > 0);
if (nread == 0)
return null;
return builder.ToString();
}
Encoding GetStringEncoding(IntPtr atom)
{
return (atom == x11Info.Atoms.XA_STRING || atom == x11Info.Atoms.OEMTEXT)
? Encoding.ASCII
: (atom == x11Info.Atoms.UTF8_STRING || atom == textUtf8Atom)
? Encoding.UTF8
: (atom == x11Info.Atoms.UTF16_STRING || atom == x11Info.Atoms.UNICODETEXT || atomNonProtocols.First(a => a.Item1 == htmlAtom).Item2 == atom)
? Encoding.Unicode
: null;
}
public void SendStatus(IntPtr source, DragDropEffects effect)
{
XEvent xevent = new XEvent();
xevent.AnyEvent.type = XEventName.ClientMessage;
xevent.AnyEvent.display = x11Info.Display;
xevent.ClientMessageEvent.window = source;
xevent.ClientMessageEvent.message_type = x11Info.Atoms.XdndStatus;
xevent.ClientMessageEvent.format = 32;
xevent.ClientMessageEvent.ptr1 = TargetWindow;
if (effect != DragDropEffects.None)
xevent.ClientMessageEvent.ptr2 = (IntPtr)1;
xevent.ClientMessageEvent.ptr5 = ActionFromEffect(effect);
XLib.XSendEvent(x11Info.Display, source, false, IntPtr.Zero, ref xevent);
//XLib.XSync(x11Info.Display,false);
Console.WriteLine("state:" + effect);
}
private void GiveFeedback(IntPtr action)
{
//GiveFeedbackEventArgs gfe = new GiveFeedbackEventArgs(EffectFromAction(drag_data.Action), true);
//Control c = MwfWindow(source);
//c.DndFeedback(gfe);
//if (gfe.UseDefaultCursors)
{
Cursor cursor = Cursors.No;
if (WillAccept)
{
// Same order as on MS
if (action == x11Info.Atoms.XdndActionCopy)
cursor = Cursors.DragCopy;
else if (action == x11Info.Atoms.XdndActionLink)
cursor = Cursors.DragLink;
else if (action == x11Info.Atoms.XdndActionMove)
cursor = Cursors.DragMove;
}
// TODO: Try not to set the cursor so much
//if (cursor.Handle != CurrentCursorHandle) {
XLib.XChangeActivePointerGrab(x11Info.Display,
EventMask.ButtonMotionMask |
EventMask.PointerMotionMask |
EventMask.ButtonPressMask |
EventMask.ButtonReleaseMask,
(IntPtr)cursor.PlatformCursor, IntPtr.Zero);
//CurrentCursorHandle = cursor.Handle;
//}
}
}
public IntPtr[] SourceSupportedList(ref XEvent xevent)
{
IntPtr[] res;
if (((int)xevent.ClientMessageEvent.ptr2 & 0x1) == 0)
{
res = new IntPtr[3];
res[0] = xevent.ClientMessageEvent.ptr3;
res[1] = xevent.ClientMessageEvent.ptr4;
res[2] = xevent.ClientMessageEvent.ptr5;
}
else
{
IntPtr type;
int format;
IntPtr count;
IntPtr remaining;
IntPtr data;
XLib.XGetWindowProperty(x11Info.Display, SourceWindow, x11Info.Atoms.XdndTypeList,
IntPtr.Zero, new IntPtr(32), false, (IntPtr)Atom.XA_ATOM,
out type, out format, out count,
out remaining, out data);
res = new IntPtr[count.ToInt32()];
for (int i = 0; i < count.ToInt32(); i++)
{
res[i] = (IntPtr)Marshal.ReadInt32(data, i *
Marshal.SizeOf(typeof(IntPtr)));
}
XLib.XFree(data);
}
return res;
}
public void SetTextData(object data, ref XEvent xevent)
{
IntPtr buffer;
int len;
string str = data as string;
//if (str == null)
//{
// IDataObject dobj = data as IDataObject;
// if (dobj == null)
// return;
// str = (string)dobj.GetData("System.String", true);
//}
if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING)
{
byte[] bytes = Encoding.ASCII.GetBytes(str);
buffer = Marshal.AllocHGlobal(bytes.Length);
len = bytes.Length;
for (int i = 0; i < len; i++)
Marshal.WriteByte(buffer, i, bytes[i]);
}
else if (xevent.SelectionRequestEvent.target == x11Info.Atoms.UTF8_STRING || xevent.SelectionRequestEvent.target == textUtf8Atom)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
buffer = Marshal.AllocHGlobal(bytes.Length);
len = bytes.Length;
for (int i = 0; i < len; i++)
Marshal.WriteByte(buffer, i, bytes[i]);
}
else
{
buffer = Marshal.StringToHGlobalAnsi(str);
len = 0;
while (Marshal.ReadByte(buffer, len) != 0)
len++;
}
SetProperty(ref xevent, buffer, len);
Marshal.FreeHGlobal(buffer);
}
public void SetHtmlData(object data, ref XEvent xevent)
{
IntPtr buffer;
int len;
string str = data as string;
if (str == null)
return;
if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING)
{
byte[] bytes = Encoding.ASCII.GetBytes(str);
buffer = Marshal.AllocHGlobal(bytes.Length);
len = bytes.Length;
for (int i = 0; i < len; i++)
Marshal.WriteByte(buffer, i, bytes[i]);
}
else
{
buffer = Marshal.StringToHGlobalAnsi(str);
len = 0;
while (Marshal.ReadByte(buffer, len) != 0)
len++;
}
SetProperty(ref xevent, buffer, len);
Marshal.FreeHGlobal(buffer);
}
public void SetFileNameData(object data, ref XEvent xevent)
{
var uri_list = data as IEnumerable<string>;
//if (uri_list == null)
//{
// IDataObject dobj = data as IDataObject;
// if (dobj == null)
// return;
// uri_list = dobj.GetData(DataFormats.FileDrop, true) as string[];
//}
if (uri_list == null)
return;
StringBuilder res = new StringBuilder();
foreach (string uri_str in uri_list)
{
Uri uri = new Uri(uri_str);
res.Append(uri.ToString());
res.Append("\r\n");
}
IntPtr buffer = Marshal.StringToHGlobalAnsi((string)res.ToString());
int len = 0;
while (Marshal.ReadByte(buffer, len) != 0)
len++;
SetProperty(ref xevent, buffer, len);
}
private void SetProperty(ref XEvent xevent, IntPtr data, int length)
{
XEvent sel = new XEvent();
sel.SelectionEvent.type = XEventName.SelectionNotify;
sel.SelectionEvent.send_event = true;
sel.SelectionEvent.display = x11Info.Display;
sel.SelectionEvent.selection = xevent.SelectionRequestEvent.selection;
sel.SelectionEvent.target = xevent.SelectionRequestEvent.target;
sel.SelectionEvent.requestor = xevent.SelectionRequestEvent.requestor;
sel.SelectionEvent.time = xevent.SelectionRequestEvent.time;
sel.SelectionEvent.property = xevent.SelectionRequestEvent.property;
XLib.XChangeProperty(x11Info.Display, xevent.SelectionRequestEvent.requestor,
xevent.SelectionRequestEvent.property,
xevent.SelectionRequestEvent.target,
8, PropertyMode.Replace, data, length);
XLib.XSendEvent(x11Info.Display, xevent.SelectionRequestEvent.requestor, true,
(IntPtr)EventMask.NoEventMask, ref sel);
return;
}
public bool HandleMouseOver()
{
IntPtr toplevel, window;
int x_root, y_root;
GetWindowsUnderPointer(out window, out toplevel, out x_root, out y_root);
if (window != LastWindow && State == DragState.Entered)
{
State = DragState.Dragging;
// TODO: Send a Leave if this is an MWF window
if (toplevel != TargetWindow)
SendLeave(TargetWindow, toplevel);
}
State = DragState.Entered;
if (toplevel != TargetWindow)
{
if (cancelHandle == null || cancelHandle.Cancel)
{
return true;
}
// Entering a new toplevel window
SendEnter(toplevel, SourceWindow, SupportedTypes);
}
else
{
// Already in a toplevel window, so send a position
SendPosition(toplevel, SourceWindow,
Action,
x_root, y_root,
IntPtr.Zero);
}
TargetWindow = toplevel;
LastWindow = window;
return true;
}
void GetWindowsUnderPointer(out IntPtr window, out IntPtr toplevel, out int x_root, out int y_root)
{
toplevel = IntPtr.Zero;
window = x11Info.RootWindow;
IntPtr root, child;
bool dnd_aware = false;
int x_temp, y_temp;
int mask_return;
int x = x_root = (int)point.X;
int y = y_root = (int)point.Y;
while (XLib.XQueryPointer(x11Info.Display, window, out root, out child,
out x_temp, out y_temp, out x, out y, out mask_return))
{
if (!dnd_aware)
{
dnd_aware = IsWindowDndAware(window);
if (dnd_aware)
{
toplevel = window;
x_root = x_temp;
y_root = y_temp;
}
}
if (child == IntPtr.Zero)
break;
window = child;
}
}
private void SendDrop(IntPtr handle, IntPtr from, IntPtr time)
{
XEvent xevent = new XEvent();
xevent.AnyEvent.type = XEventName.ClientMessage;
xevent.AnyEvent.display = x11Info.Display;
xevent.ClientMessageEvent.window = handle;
xevent.ClientMessageEvent.message_type = x11Info.Atoms.XdndDrop;
xevent.ClientMessageEvent.format = 32;
xevent.ClientMessageEvent.ptr1 = from;
xevent.ClientMessageEvent.ptr3 = time;
XLib.XSendEvent(x11Info.Display, handle, false, IntPtr.Zero, ref xevent);
//dropped = true;
}
private void SendPosition(IntPtr handle, IntPtr from, IntPtr action, int x, int y, IntPtr time)
{
XEvent xevent = new XEvent();
xevent.AnyEvent.type = XEventName.ClientMessage;
xevent.AnyEvent.display = x11Info.Display;
xevent.ClientMessageEvent.window = handle;
xevent.ClientMessageEvent.message_type = x11Info.Atoms.XdndPosition;
xevent.ClientMessageEvent.format = 32;
xevent.ClientMessageEvent.ptr1 = from;
xevent.ClientMessageEvent.ptr3 = (IntPtr)((x << 16) | (y & 0xFFFF));
xevent.ClientMessageEvent.ptr4 = time;
xevent.ClientMessageEvent.ptr5 = action;
XLib.XSendEvent(x11Info.Display, handle, false, IntPtr.Zero, ref xevent);
}
private void SendLeave(IntPtr handle, IntPtr from)
{
XEvent xevent = new XEvent();
xevent.AnyEvent.type = XEventName.ClientMessage;
xevent.AnyEvent.display = x11Info.Display;
xevent.ClientMessageEvent.window = handle;
xevent.ClientMessageEvent.message_type = x11Info.Atoms.XdndLeave;
xevent.ClientMessageEvent.format = 32;
xevent.ClientMessageEvent.ptr1 = from;
XLib.XSendEvent(x11Info.Display, handle, false, IntPtr.Zero, ref xevent);
}
public void SendFinished()
{
XEvent xevent = new XEvent();
xevent.AnyEvent.type = XEventName.ClientMessage;
xevent.AnyEvent.display = x11Info.Display;
xevent.ClientMessageEvent.window = SourceWindow;
xevent.ClientMessageEvent.message_type = x11Info.Atoms.XdndFinished;
xevent.ClientMessageEvent.format = 32;
xevent.ClientMessageEvent.ptr1 = TargetWindow;
XLib.XSendEvent(x11Info.Display, SourceWindow, false, IntPtr.Zero, ref xevent);
XLib.XFlush(x11Info.Display);
}
private bool IsWindowDndAware(IntPtr handle)
{
bool res = true;
// Check the version number, we need greater than 3
IntPtr actual;
int format;
IntPtr count;
IntPtr remaining;
IntPtr data = IntPtr.Zero;
XLib.XGetWindowProperty(x11Info.Display, handle, x11Info.Atoms.XdndAware, IntPtr.Zero, new IntPtr(0x8000000), false,
(IntPtr)Atom.XA_ATOM, out actual, out format,
out count, out remaining, out data);
if (actual != (IntPtr)Atom.XA_ATOM || format != 32 ||
count.ToInt32() == 0 || data == IntPtr.Zero)
{
if (data != IntPtr.Zero)
XLib.XFree(data);
return false;
}
int version = Marshal.ReadInt32(data, 0);
if (version < 3)
{
Console.Error.WriteLine("XDND Version too old (" + version + ").");
XLib.XFree(data);
return false;
}
// First type is actually the XDND version
if (count.ToInt32() > 1)
{
res = false;
for (int i = 1; i < count.ToInt32(); i++)
{
IntPtr type = (IntPtr)Marshal.ReadInt32(data, i *
Marshal.SizeOf(typeof(int)));
for (int j = 0; j < SupportedTypes.Length; j++)
{
if (SupportedTypes[j] == type)
{
res = true;
break;
}
}
}
}
XLib.XFree(data);
return res;
}
private enum DragState
{
None,
Beginning,
Dragging,
Entered
}
}
}