mirror of
				https://gitee.com/csharpui/CPF.git
				synced 2025-11-01 00:46:56 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			323 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Text;
 | |
| using CPF.Input;
 | |
| using System.Threading.Tasks;
 | |
| using System.Linq;
 | |
| using static CPF.Linux.XLib;
 | |
| using System.Runtime.InteropServices;
 | |
| 
 | |
| namespace CPF.Linux
 | |
| {
 | |
|     class X11Clipboard : IClipboard
 | |
|     {
 | |
|         private readonly X11Info _x11;
 | |
|         private string _storedString;
 | |
|         private string _storedHtml;
 | |
|         private XWindow window;
 | |
|         private CancelHandle _requestedFormatsHandle;
 | |
|         private CancelHandle _requestedTextHandle;
 | |
|         private readonly IntPtr[] _textAtoms;
 | |
| 
 | |
|         public X11Clipboard()
 | |
|         {
 | |
|             _x11 = LinuxPlatform.Platform.Info;
 | |
|             window = new XWindow { EventAction = OnXEvent };
 | |
|             //_saveTargetsAtom = XInternAtom(_x11.Display, "PENGUIN", false);
 | |
|             _textAtoms = new[]
 | |
|             {
 | |
|                 _x11.Atoms.XA_STRING,
 | |
|                 _x11.Atoms.OEMTEXT,
 | |
|                 _x11.Atoms.UTF8_STRING,
 | |
|                 _x11.Atoms.UTF16_STRING
 | |
|             }.Where(a => a != IntPtr.Zero).ToArray();
 | |
|         }
 | |
| 
 | |
|         void OnXEvent(ref XEvent ev)
 | |
|         {
 | |
|             if (ev.type == XEventName.SelectionRequest)
 | |
|             {
 | |
|                 var sel = ev.SelectionRequestEvent;
 | |
|                 var resp = new XEvent
 | |
|                 {
 | |
|                     SelectionEvent =
 | |
|                     {
 | |
|                         type = XEventName.SelectionNotify,
 | |
|                         send_event = true,
 | |
|                         display = _x11.Display,
 | |
|                         selection = sel.selection,
 | |
|                         target = sel.target,
 | |
|                         requestor = sel.requestor,
 | |
|                         time = sel.time,
 | |
|                         property = IntPtr.Zero
 | |
|                     }
 | |
|                 };
 | |
|                 if (sel.selection == _x11.Atoms.CLIPBOARD)
 | |
|                 {
 | |
|                     resp.SelectionEvent.property = WriteTargetToProperty(sel.target, sel.requestor, sel.property);
 | |
|                 }
 | |
| 
 | |
|                 XSendEvent(_x11.Display, sel.requestor, false, new IntPtr((int)EventMask.NoEventMask), ref resp);
 | |
|                 XFlush(_x11.Display);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Encoding GetStringEncoding(IntPtr atom)
 | |
|         {
 | |
|             return (atom == _x11.Atoms.XA_STRING
 | |
|                     || atom == _x11.Atoms.OEMTEXT)
 | |
|                 ? Encoding.ASCII
 | |
|                 : atom == _x11.Atoms.UTF8_STRING
 | |
|                     ? Encoding.UTF8
 | |
|                     : atom == _x11.Atoms.UTF16_STRING
 | |
|                         ? Encoding.Unicode
 | |
|                         : null;
 | |
|         }
 | |
| 
 | |
|         unsafe IntPtr WriteTargetToProperty(IntPtr target, IntPtr window, IntPtr property)
 | |
|         {
 | |
|             Encoding textEnc;
 | |
|             if (target == _x11.Atoms.TARGETS)
 | |
|             {
 | |
|                 var atoms = _textAtoms;
 | |
|                 atoms = atoms.Concat(new[] { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE })
 | |
|                     .ToArray();
 | |
|                 if (!string.IsNullOrEmpty(_storedHtml))
 | |
|                 {
 | |
|                     atoms = atoms.Concat(new[] { DataObject.htmlAtom })
 | |
|                         .ToArray();
 | |
|                 }
 | |
|                 XChangeProperty(_x11.Display, window, property, _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length);
 | |
|                 return property;
 | |
|             }
 | |
|             else if (target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
 | |
|             {
 | |
|                 return property;
 | |
|             }
 | |
|             else if ((textEnc = GetStringEncoding(target)) != null)
 | |
|             {
 | |
| 
 | |
|                 var data = textEnc.GetBytes(_storedString ?? "");
 | |
|                 //var ptr = Marshal.AllocHGlobal(data.Length);
 | |
|                 //Marshal.Copy(data, 0, ptr, data.Length);
 | |
|                 //XChangeProperty(_x11.Display, window, property, target, 8,
 | |
|                 //    PropertyMode.Replace,
 | |
|                 //    (void*)ptr, data.Length);
 | |
|                 fixed (void* pdata = data)
 | |
|                     XChangeProperty(_x11.Display, window, property, target, 8,
 | |
|                         PropertyMode.Replace,
 | |
|                         pdata, data.Length);
 | |
|                 return property;
 | |
|             }
 | |
|             else if (target == _x11.Atoms.MULTIPLE && _x11.Atoms.MULTIPLE != IntPtr.Zero)
 | |
|             {
 | |
|                 XGetWindowProperty(_x11.Display, window, property, IntPtr.Zero, new IntPtr(0x7fffffff), false,
 | |
|                     _x11.Atoms.ATOM_PAIR, out _, out var actualFormat, out var nitems, out _, out var prop);
 | |
|                 if (nitems == IntPtr.Zero)
 | |
|                     return IntPtr.Zero;
 | |
|                 if (actualFormat == 32)
 | |
|                 {
 | |
|                     var data = (IntPtr*)prop.ToPointer();
 | |
|                     for (var c = 0; c < nitems.ToInt32(); c += 2)
 | |
|                     {
 | |
|                         var subTarget = data[c];
 | |
|                         var subProp = data[c + 1];
 | |
|                         var converted = WriteTargetToProperty(subTarget, window, subProp);
 | |
|                         data[c + 1] = converted;
 | |
|                     }
 | |
| 
 | |
|                     XChangeProperty(_x11.Display, window, property, _x11.Atoms.ATOM_PAIR, 32, PropertyMode.Replace,
 | |
|                         prop.ToPointer(), nitems.ToInt32());
 | |
|                 }
 | |
| 
 | |
|                 XFree(prop);
 | |
| 
 | |
|                 return property;
 | |
|             }
 | |
|             else if (target == DataObject.htmlAtom)
 | |
|             {
 | |
|                 var data = Encoding.UTF8.GetBytes(_storedHtml ?? "");
 | |
|                 fixed (void* pdata = data)
 | |
|                     XChangeProperty(_x11.Display, window, property, target, 8, PropertyMode.Replace, pdata, data.Length);
 | |
|                 return property;
 | |
|             }
 | |
|             else
 | |
|                 return IntPtr.Zero;
 | |
|         }
 | |
| 
 | |
|         private unsafe bool OnEvent(ref XEvent ev)
 | |
|         {
 | |
|             if (ev.type == XEventName.SelectionNotify && ev.SelectionEvent.selection == _x11.Atoms.CLIPBOARD)
 | |
|             {
 | |
|                 var sel = ev.SelectionEvent;
 | |
|                 if (sel.property == IntPtr.Zero)
 | |
|                 {
 | |
|                     _requestedFormatsHandle.SetResult(null);
 | |
|                     _requestedTextHandle.SetResult(null);
 | |
|                 }
 | |
|                 XGetWindowProperty(_x11.Display, window.Handle, sel.property, IntPtr.Zero, new IntPtr(0x7fffffff), true, (IntPtr)Atom.AnyPropertyType,
 | |
|                     out var actualAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop);
 | |
|                 Encoding textEnc = null;
 | |
|                 if (nitems == IntPtr.Zero)
 | |
|                 {
 | |
|                     _requestedFormatsHandle.SetResult(null);
 | |
|                     _requestedTextHandle.SetResult(null);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     if (sel.property == _x11.Atoms.TARGETS)
 | |
|                     {
 | |
|                         if (actualFormat != 32)
 | |
|                         {
 | |
|                             _requestedFormatsHandle.SetResult(null);
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             var formats = new IntPtr[nitems.ToInt32()];
 | |
|                             Marshal.Copy(prop, formats, 0, formats.Length);
 | |
|                             _requestedFormatsHandle.SetResult(formats);
 | |
|                             //_requestedFormatsTcs?.TrySetResult(formats);
 | |
|                         }
 | |
|                     }
 | |
|                     else if ((textEnc = GetStringEncoding(sel.property)) != null)
 | |
|                     {
 | |
|                         var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32());
 | |
|                         //_requestedTextTcs?.TrySetResult(text);
 | |
|                         _requestedTextHandle.SetResult(text);
 | |
|                     }
 | |
|                     else if (sel.property == DataObject.htmlAtom)
 | |
|                     {
 | |
|                         var html = Encoding.UTF8.GetString((byte*)prop.ToPointer(), nitems.ToInt32());
 | |
|                         _requestedTextHandle.SetResult(html);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 XFree(prop);
 | |
|             }
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         IntPtr[] SendFormatRequest()
 | |
|         {
 | |
|             //if (_requestedFormatsTcs == null || _requestedFormatsTcs.Task.IsCompleted)
 | |
|             //    _requestedFormatsTcs = new TaskCompletionSource<IntPtr[]>();
 | |
|             if (_requestedFormatsHandle == null || _requestedFormatsHandle.Cancel)
 | |
|             {
 | |
|                 _requestedFormatsHandle = new CancelHandle();
 | |
|             }
 | |
|             XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, _x11.Atoms.TARGETS, _x11.Atoms.TARGETS, window.Handle,
 | |
|                 IntPtr.Zero);
 | |
|             //while (!_requestedFormatsTcs.Task.IsCompleted)
 | |
|             //{
 | |
|             //    XNextEvent(_x11.Display, out var xev);
 | |
|             //    OnEvent(xev);
 | |
|             //    LinuxPlatform.Platform.OnEvent(xev);
 | |
|             //}
 | |
|             LinuxPlatform.Platform.RunMainLoop(_requestedFormatsHandle, OnEvent);
 | |
|             //return _requestedFormatsTcs.Task;
 | |
|             return (IntPtr[])_requestedFormatsHandle.Data;
 | |
|         }
 | |
| 
 | |
|         string SendTextRequest(IntPtr format)
 | |
|         {
 | |
|             //if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted)
 | |
|             //    _requestedTextTcs = new TaskCompletionSource<string>();
 | |
|             if (_requestedTextHandle == null || _requestedFormatsHandle.Cancel)
 | |
|             {
 | |
|                 _requestedTextHandle = new CancelHandle();
 | |
|             }
 | |
|             XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, window.Handle, IntPtr.Zero);
 | |
|             //while (!_requestedTextTcs.Task.IsCompleted)
 | |
|             //{
 | |
|             //    XNextEvent(_x11.Display, out var xev);
 | |
|             //    OnEvent(xev);
 | |
|             //    LinuxPlatform.Platform.OnEvent(xev);
 | |
|             //}
 | |
|             //return _requestedTextTcs.Task;
 | |
|             LinuxPlatform.Platform.RunMainLoop(_requestedTextHandle, OnEvent);
 | |
|             return (string)_requestedTextHandle.Data;
 | |
|         }
 | |
| 
 | |
|         public void Clear()
 | |
|         {
 | |
|             SetData((DataFormat.Text, null));
 | |
|         }
 | |
| 
 | |
|         public bool Contains(DataFormat dataFormat)
 | |
|         {
 | |
|             if (dataFormat == DataFormat.Text)
 | |
|             {
 | |
|                 return GetData(DataFormat.Text) != null;
 | |
|             }
 | |
|             else if (dataFormat == DataFormat.Html)
 | |
|             {
 | |
|                 return GetData(DataFormat.Html) != null;
 | |
|             }
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         string GetText()
 | |
|         {
 | |
|             if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero)
 | |
|                 return null;
 | |
|             var res = SendFormatRequest();
 | |
|             var target = _x11.Atoms.UTF8_STRING;
 | |
|             if (res != null)
 | |
|             {
 | |
|                 var preferredFormats = new[] { _x11.Atoms.UTF16_STRING, _x11.Atoms.UTF8_STRING, _x11.Atoms.XA_STRING };
 | |
|                 foreach (var pf in preferredFormats)
 | |
|                     if (res.Contains(pf))
 | |
|                     {
 | |
|                         target = pf;
 | |
|                         break;
 | |
|                     }
 | |
|             }
 | |
| 
 | |
|             return SendTextRequest(target);
 | |
|         }
 | |
|         string GetHtml()
 | |
|         {
 | |
|             if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero)
 | |
|                 return null;
 | |
|             var res = SendFormatRequest();
 | |
| 
 | |
|             return SendTextRequest(DataObject.htmlAtom);
 | |
|         }
 | |
| 
 | |
|         public object GetData(DataFormat dataFormat)
 | |
|         {
 | |
|             if (dataFormat == DataFormat.Text)
 | |
|             {
 | |
|                 return GetText();
 | |
|             }
 | |
|             if (dataFormat == DataFormat.Html)
 | |
|             {
 | |
|                 return GetHtml();
 | |
|             }
 | |
|             throw new NotImplementedException("暂时不支持:" + dataFormat);
 | |
|         }
 | |
| 
 | |
|         public void SetData(params (DataFormat, object)[] data)
 | |
|         {
 | |
|             _storedHtml = null;
 | |
|             _storedString = null;
 | |
|             foreach (var item in data)
 | |
|             {
 | |
|                 if (item.Item1 == DataFormat.Text)
 | |
|                 {
 | |
|                     _storedString = item.Item2 == null ? null : item.Item2.ToString();
 | |
|                     //break;
 | |
|                 }
 | |
|                 else if (item.Item1 == DataFormat.Html)
 | |
|                 {
 | |
|                     _storedHtml = item.Item2?.ToString();
 | |
|                 }
 | |
|             }
 | |
|             if (data.Length > 0)
 | |
|             {
 | |
|                 XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, window.Handle, IntPtr.Zero);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | 
