mirror of
https://gitee.com/csharpui/CPF.git
synced 2025-04-05 08:37:19 +08:00
789 lines
30 KiB
C#
789 lines
30 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Runtime.InteropServices;
|
||
using CPF;
|
||
using CPF.Drawing;
|
||
using CPF.Input;
|
||
using CPF.Platform;
|
||
using System.Threading.Tasks;
|
||
using CPF.Controls;
|
||
using System.Linq;
|
||
using System.Diagnostics;
|
||
using static CPF.Linux.XLib;
|
||
using static CPF.Linux.Glib;
|
||
using static CPF.Linux.Gtk;
|
||
using System.Net.Sockets;
|
||
using System.Net;
|
||
using System.IO;
|
||
|
||
namespace CPF.Linux
|
||
{
|
||
public class LinuxPlatform : RuntimePlatform
|
||
{
|
||
public static LinuxPlatform Platform;
|
||
|
||
////[DllImport("libXlibDemo.so")]
|
||
////public static extern IntPtr OpenIM(IntPtr dpy);
|
||
////[DllImport("libXlibDemo.so")]
|
||
////public static extern IntPtr CreateIC(IntPtr xim, IntPtr win);
|
||
//[DllImport("libXlibDemo.so")]
|
||
//public static extern void Move(IntPtr win, IntPtr dpy, ref XEvent xEvent);
|
||
|
||
///// <summary>
|
||
///// https://blog.csdn.net/qq_32768743/article/details/90605212
|
||
///// </summary>
|
||
//public static void Test()
|
||
//{
|
||
// Console.WriteLine(setlocale(6, ""));
|
||
// var dpy = XOpenDisplay(IntPtr.Zero);
|
||
// XSupportsLocale();
|
||
// Console.WriteLine(XSetLocaleModifiers(""));
|
||
|
||
// IntPtr default_string = IntPtr.Zero;
|
||
// var fontset = XCreateFontSet(dpy, "-*-*-medium-r-normal-*-*-120-*-*-*-*", out var missing_charsets, out var num_missing_charsets, ref default_string);
|
||
|
||
// Console.WriteLine("default_string:" + Marshal.PtrToStringUni(default_string));
|
||
// //Thread.Sleep(10000);
|
||
// var screen = XDefaultScreen(dpy);
|
||
// var win = XCreateSimpleWindow(dpy, XRootWindow(dpy, screen), 0, 0, 500, 200,
|
||
// 2, XBlackPixel(dpy, screen), XWhitePixel(dpy, screen));
|
||
// var gc = XCreateGC(dpy, win, 0, IntPtr.Zero);
|
||
// XSetForeground(dpy, gc, XWhitePixel(dpy, screen));
|
||
// XSetBackground(dpy, gc, XBlackPixel(dpy, screen));
|
||
// var im = XOpenIM(dpy, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||
// var list = XVaCreateNestedList(0, XNames.XNFontSet, fontset, IntPtr.Zero);
|
||
// var best_style = XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing;
|
||
// Console.WriteLine(string.Join("-", X11Window.GetSupportedInputStyles(im)));
|
||
// var ic = XCreateIC(im,
|
||
// XNames.XNInputStyle, best_style,
|
||
// XNames.XNClientWindow, win,
|
||
// IntPtr.Zero);
|
||
// XFree(list);
|
||
|
||
// long im_event_mask = 0;
|
||
// XGetICValues(ic, XNames.XNFilterEvents, ref im_event_mask, IntPtr.Zero);
|
||
// XSelectInput(dpy, win, (IntPtr)((long)(XEventMask.ExposureMask | XEventMask.KeyPressMask
|
||
// | XEventMask.StructureNotifyMask) | im_event_mask));
|
||
// XSetICFocus(ic);
|
||
// XMapWindow(dpy, win);
|
||
// //XRectangle preedit_area = new XRectangle();
|
||
// //XRectangle status_area = new XRectangle();
|
||
|
||
// while (true)
|
||
// {
|
||
// XNextEvent(dpy, out var xEvent);
|
||
// if (XFilterEvent(ref xEvent, IntPtr.Zero))
|
||
// continue;
|
||
|
||
// //switch (xEvent.type)
|
||
// //{
|
||
// // case XEventName.KeyPress:
|
||
|
||
// // break;
|
||
// // case XEventName.ConfigureNotify:
|
||
|
||
// // if ((best_style & XIMProperties.XIMPreeditArea) != 0)
|
||
// // {
|
||
// // preedit_area.width = (ushort)(xEvent.ConfigureEvent.width * 4 / 5);
|
||
// // preedit_area.height = 0;
|
||
// // GetPreferredGeometry(ic, XNames.XNPreeditAttributes, ref preedit_area);
|
||
// // preedit_area.x = (short)(xEvent.ConfigureEvent.width - preedit_area.width);
|
||
// // preedit_area.y = (short)(xEvent.ConfigureEvent.height - preedit_area.height);
|
||
// // SetGeometry(ic, XNames.XNPreeditAttributes, preedit_area);
|
||
// // }
|
||
// // if ((best_style & XIMProperties.XIMStatusArea) != 0)
|
||
// // {
|
||
// // status_area.width = (ushort)(xEvent.ConfigureEvent.width / 5);
|
||
// // status_area.height = 0;
|
||
// // GetPreferredGeometry(ic, XNames.XNStatusAttributes, ref status_area);
|
||
// // status_area.x = 0;
|
||
// // status_area.y = (short)(xEvent.ConfigureEvent.height - status_area.height);
|
||
// // SetGeometry(ic, XNames.XNStatusAttributes, status_area);
|
||
// // }
|
||
// // break;
|
||
// // default:
|
||
// // break;
|
||
// //}
|
||
|
||
// }
|
||
|
||
|
||
//}
|
||
|
||
//static void GetPreferredGeometry(IntPtr ic, string name, ref XRectangle area)
|
||
////XIC ic;
|
||
////char *name; /* XNPreEditAttributes or XNStatusAttributes */
|
||
////XRectangle *area; /* the constraints on the area */
|
||
//{
|
||
// var list = XVaCreateNestedList(0, "areaNeeded", ref area, IntPtr.Zero);
|
||
// /* set the constraints */
|
||
// XSetICValues(ic, name, list, IntPtr.Zero);
|
||
// /* Now query the preferred size */
|
||
// /* The Xsi input method, Xwnmo, seems to ignore the constraints, */
|
||
// /* but we’re not going to try to enforce them here. */
|
||
// XGetICValues(ic, name, list, IntPtr.Zero);
|
||
// XFree(list);
|
||
//}
|
||
////XIC ic;
|
||
////char *name; /* XNPreEditAttributes or XNStatusAttributes */
|
||
////XRectangle *area; /* the actual area to set */
|
||
//static unsafe void SetGeometry(IntPtr ic, string name, XRectangle area)
|
||
//{
|
||
// var list = XVaCreateNestedList(0, "area", ref area, IntPtr.Zero);
|
||
// XSetICValues(ic, name, list, IntPtr.Zero);
|
||
// XFree(list);
|
||
//}
|
||
|
||
|
||
enum EventCodes
|
||
{
|
||
X11 = 1,
|
||
Signal = 2
|
||
}
|
||
|
||
//private int _sigread, _sigwrite;
|
||
//private int _epoll;
|
||
private object _lock = new object();
|
||
//private bool _signaled;
|
||
|
||
static Pollfd[] pollfds; // For watching the X11 socket
|
||
static Socket listen; //
|
||
static Socket wake; //
|
||
static Socket wake_receive; //
|
||
static byte[] network_buffer; //
|
||
/// <summary>
|
||
/// 启用触摸事件,之所有加上这个,因为部分Linux对XI2支持有问题
|
||
/// </summary>
|
||
public bool EnabledTouch { get; set; }
|
||
|
||
public unsafe LinuxPlatform()
|
||
{
|
||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||
{
|
||
Console.WriteLine($"系统架构:{RuntimeInformation.OSArchitecture}");
|
||
Console.WriteLine($"系统名称:{RuntimeInformation.OSDescription}");
|
||
Console.WriteLine($"进程架构:{RuntimeInformation.ProcessArchitecture}");
|
||
Console.WriteLine($"是否64位操作系统:{Environment.Is64BitOperatingSystem}");
|
||
Console.WriteLine("CPU CORE:" + Environment.ProcessorCount);
|
||
Console.WriteLine("HostName:" + Environment.MachineName);
|
||
Console.WriteLine("Version:" + Environment.OSVersion);
|
||
Console.WriteLine("CPF Version:" + typeof(LinuxPlatform).Assembly.GetName().Version);
|
||
|
||
Platform = this;
|
||
XInitThreads();
|
||
Display = XOpenDisplay(IntPtr.Zero);
|
||
//Console.WriteLine(setlocale(6, ""));
|
||
//DeferredDisplay = XOpenDisplay(IntPtr.Zero);
|
||
if (Display == IntPtr.Zero)
|
||
throw new Exception("XOpenDisplay failed");
|
||
XError.Init();
|
||
Info = new X11Info(Display, DeferredDisplay);
|
||
|
||
Thread.CurrentThread.Name = "主线程";
|
||
_cursors = Enum.GetValues(typeof(CursorFontShape)).Cast<CursorFontShape>()
|
||
.ToDictionary(id => id, id => XLib.XCreateFontCursor(Display, id));
|
||
|
||
|
||
if (Info.XInputVersion != null && EnabledTouch)
|
||
{
|
||
var xi2 = new XI2Manager();
|
||
if (xi2.Init(this))
|
||
XI2 = xi2;
|
||
}
|
||
|
||
var fd = XLib.XConnectionNumber(Display);
|
||
//var ev = new epoll_event()
|
||
//{
|
||
// events = EPOLLIN,
|
||
// data = { u32 = (int)EventCodes.X11 }
|
||
//};
|
||
//_epoll = epoll_create1(0);
|
||
//if (_epoll == -1)
|
||
// throw new Exception("epoll_create1 failed");
|
||
|
||
//if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, ref ev) == -1)
|
||
// throw new Exception("Unable to attach X11 connection handle to epoll");
|
||
|
||
//var fds = stackalloc int[2];
|
||
////pipe2(fds, O_NONBLOCK);
|
||
//if (pipe2(fds, O_NONBLOCK) == -1)
|
||
// throw new Exception("Unable to create X11 pipe");
|
||
|
||
//_sigread = fds[0];
|
||
//_sigwrite = fds[1];
|
||
|
||
//ev = new epoll_event
|
||
//{
|
||
// events = EPOLLIN,
|
||
// data = { u32 = (int)EventCodes.Signal }
|
||
//};
|
||
//if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1)
|
||
// throw new Exception("Unable to attach signal pipe to epoll");
|
||
|
||
|
||
// For sleeping on the X11 socket
|
||
listen = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
|
||
IPEndPoint ep = new IPEndPoint(IPAddress.Loopback, 0);
|
||
listen.Bind(ep);
|
||
listen.Listen(1);
|
||
|
||
// To wake up when a timer is ready
|
||
network_buffer = new byte[10];
|
||
|
||
wake = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
|
||
wake.Connect(listen.LocalEndPoint);
|
||
|
||
// Make this non-blocking, so it doesn't
|
||
// deadlock if too many wakes are sent
|
||
// before the wake_receive end is polled
|
||
wake.Blocking = false;
|
||
|
||
wake_receive = listen.Accept();
|
||
|
||
pollfds = new Pollfd[2];
|
||
pollfds[0] = new Pollfd();
|
||
pollfds[0].fd = fd;
|
||
pollfds[0].events = PollEvents.POLLIN;
|
||
|
||
pollfds[1] = new Pollfd();
|
||
pollfds[1].fd = wake_receive.Handle.ToInt32();
|
||
pollfds[1].events = PollEvents.POLLIN;
|
||
|
||
|
||
XrmInitialize(); /* Need to initialize the DB before calling Xrm* functions */
|
||
|
||
}
|
||
}
|
||
public XI2Manager XI2;
|
||
public X11Info Info { get; private set; }
|
||
public IntPtr DeferredDisplay { get; set; }
|
||
public IntPtr Display { get; set; }
|
||
|
||
public override PixelPoint MousePosition
|
||
{
|
||
get
|
||
{
|
||
var po = GetCursorPos(Info);
|
||
//Console.WriteLine(po);
|
||
return new PixelPoint(po.x, po.y);
|
||
}
|
||
}
|
||
|
||
public override TimeSpan DoubleClickTime
|
||
{
|
||
get
|
||
{
|
||
return TimeSpan.FromSeconds(0.4);
|
||
}
|
||
}
|
||
|
||
|
||
public override IPopupImpl CreatePopup()
|
||
{
|
||
return new PopWindow();
|
||
}
|
||
|
||
public override IWindowImpl CreateWindow()
|
||
{
|
||
return new X11Window();
|
||
}
|
||
|
||
DataObject dataObject;
|
||
public override DragDropEffects DoDragDrop(DragDropEffects allowedEffects, params (DataFormat, object)[] data)
|
||
{
|
||
dataObject = new DataObject();
|
||
dataObject.StartDrag(allowedEffects, data);
|
||
|
||
|
||
return allowedEffects;
|
||
}
|
||
|
||
public override IReadOnlyList<Screen> GetAllScreen()
|
||
{
|
||
return ScreenImpl.Screens;
|
||
}
|
||
|
||
public override IClipboard GetClipboard()
|
||
{
|
||
return new X11Clipboard();
|
||
}
|
||
|
||
|
||
|
||
private Dictionary<CursorFontShape, IntPtr> _cursors;
|
||
private static readonly Dictionary<Cursors, CursorFontShape> s_mapping =
|
||
new Dictionary<Cursors, CursorFontShape>
|
||
{
|
||
{Cursors.Arrow, CursorFontShape.XC_top_left_arrow},
|
||
{Cursors.Cross, CursorFontShape.XC_cross},
|
||
{Cursors.Hand, CursorFontShape.XC_hand1},
|
||
{Cursors.Help, CursorFontShape.XC_question_arrow},
|
||
{Cursors.Ibeam, CursorFontShape.XC_xterm},
|
||
{Cursors.No, CursorFontShape.XC_X_cursor},
|
||
{Cursors.Wait, CursorFontShape.XC_watch},
|
||
{Cursors.AppStarting, CursorFontShape.XC_watch},
|
||
{Cursors.BottomSide, CursorFontShape.XC_bottom_side},
|
||
{Cursors.DragCopy, CursorFontShape.XC_center_ptr},
|
||
{Cursors.DragLink, CursorFontShape.XC_fleur},
|
||
{Cursors.DragMove, CursorFontShape.XC_diamond_cross},
|
||
{Cursors.LeftSide, CursorFontShape.XC_left_side},
|
||
{Cursors.RightSide, CursorFontShape.XC_right_side},
|
||
{Cursors.SizeAll, CursorFontShape.XC_sizing},
|
||
{Cursors.TopSide, CursorFontShape.XC_top_side},
|
||
{Cursors.UpArrow, CursorFontShape.XC_sb_up_arrow},
|
||
{Cursors.BottomLeftCorner, CursorFontShape.XC_bottom_left_corner},
|
||
{Cursors.BottomRightCorner, CursorFontShape.XC_bottom_right_corner},
|
||
{Cursors.SizeNorthSouth, CursorFontShape.XC_sb_v_double_arrow},
|
||
{Cursors.SizeWestEast, CursorFontShape.XC_sb_h_double_arrow},
|
||
{Cursors.TopLeftCorner, CursorFontShape.XC_top_left_corner},
|
||
{Cursors.TopRightCorner, CursorFontShape.XC_top_right_corner},
|
||
};
|
||
|
||
public override object GetCursor(Cursors cursorType)
|
||
{
|
||
IntPtr handle;
|
||
//if (cursorType == Cursors.None)
|
||
//{
|
||
// handle = _nullCursor;
|
||
//}
|
||
//else
|
||
//{
|
||
handle = s_mapping.TryGetValue(cursorType, out var shape)
|
||
? _cursors[shape]
|
||
: _cursors[CursorFontShape.XC_top_left_arrow];
|
||
//}
|
||
return handle;
|
||
}
|
||
|
||
public override SynchronizationContext GetSynchronizationContext()
|
||
{
|
||
return new LinuxSynchronizationContext();
|
||
}
|
||
|
||
static Dictionary<KeyGesture, PlatformHotkey> keyValuePairs = new Dictionary<KeyGesture, PlatformHotkey>() {
|
||
{ new KeyGesture(Keys.C,InputModifiers.Control),PlatformHotkey.Copy},
|
||
{ new KeyGesture(Keys.X,InputModifiers.Control),PlatformHotkey.Cut},
|
||
{ new KeyGesture(Keys.V,InputModifiers.Control),PlatformHotkey.Paste},
|
||
{ new KeyGesture(Keys.Y,InputModifiers.Control),PlatformHotkey.Redo},
|
||
{ new KeyGesture(Keys.A,InputModifiers.Control),PlatformHotkey.SelectAll},
|
||
{ new KeyGesture(Keys.Z,InputModifiers.Control),PlatformHotkey.Undo},
|
||
};
|
||
public override PlatformHotkey Hotkey(KeyGesture keyGesture)
|
||
{
|
||
keyValuePairs.TryGetValue(keyGesture, out PlatformHotkey platformHotkey);
|
||
return platformHotkey;
|
||
}
|
||
|
||
internal Dictionary<IntPtr, XWindow> windows = new Dictionary<IntPtr, XWindow>();
|
||
internal bool run = true;
|
||
public unsafe override void Run()
|
||
{
|
||
//X11Window x11Window = new X11Window();
|
||
//x11Window.SetVisible(true);
|
||
DoTask();
|
||
while (run)
|
||
{
|
||
XSync(Display, false);
|
||
while (XPending(Display) != 0)
|
||
{
|
||
XNextEvent(Display, out var xev);
|
||
if (XFilterEvent(ref xev, IntPtr.Zero))
|
||
{
|
||
continue;
|
||
}
|
||
OnEvent(ref xev, null);
|
||
}
|
||
//Invoke();
|
||
//Thread.Sleep(1);
|
||
//lock (_lock)
|
||
//{
|
||
// _signaled = false;
|
||
// int buf = 0;
|
||
// while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0)
|
||
// {
|
||
// }
|
||
//}
|
||
DoTask();
|
||
Wait();
|
||
}
|
||
XCloseDisplay(Display);
|
||
}
|
||
|
||
public override void Run(CancellationToken cancellation)
|
||
{
|
||
//Console.WriteLine("开始循环");
|
||
while (!cancellation.IsCancellationRequested && run)
|
||
{
|
||
XSync(Display, false);
|
||
while (XPending(Display) != 0)
|
||
{
|
||
XNextEvent(Display, out var xev);
|
||
if (XFilterEvent(ref xev, IntPtr.Zero))
|
||
{
|
||
continue;
|
||
}
|
||
OnEvent(ref xev, null);
|
||
}
|
||
DoTask();
|
||
if (cancellation.IsCancellationRequested)
|
||
{
|
||
break;
|
||
}
|
||
Wait();
|
||
}
|
||
//Console.WriteLine("结束循环" + cancellation.IsCancellationRequested + run);
|
||
}
|
||
private unsafe void Wait()
|
||
{
|
||
XFlush(Display);
|
||
if (XPending(Display) == 0)
|
||
{
|
||
//if (run)
|
||
//{
|
||
//epoll_event ev;
|
||
// epoll_wait(_epoll, &ev, 1, -1);//-1永久阻塞,直到有信号,
|
||
//}
|
||
|
||
poll(pollfds, (uint)pollfds.Length, -1);
|
||
// Clean out buffer, so we're not busy-looping on the same data
|
||
//if (length == pollfds.Length)
|
||
{
|
||
if (pollfds[1].revents != 0)
|
||
wake_receive.Receive(network_buffer, 0, 1, SocketFlags.None);
|
||
|
||
}
|
||
}
|
||
CPF.Threading.DispatcherTimer.SetTimeTick();
|
||
}
|
||
|
||
HashSet<XEventHandler> handlers = new HashSet<XEventHandler>();
|
||
public unsafe void RunMainLoop(CancelHandle cancelHandle, XEventHandler action)
|
||
{
|
||
handlers.Add(action);
|
||
while (!cancelHandle.Cancel && run)
|
||
{
|
||
XSync(Display, false);
|
||
while (XPending(Display) != 0)
|
||
{
|
||
XNextEvent(Display, out var xev);
|
||
if (XFilterEvent(ref xev, IntPtr.Zero))
|
||
{
|
||
continue;
|
||
}
|
||
if (!action(ref xev))
|
||
{
|
||
OnEvent(ref xev, action);
|
||
}
|
||
}
|
||
//Invoke();
|
||
//Thread.Sleep(1);
|
||
//lock (_lock)
|
||
//{
|
||
// _signaled = false;
|
||
// int buf = 0;
|
||
// while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0)
|
||
// {
|
||
// }
|
||
//}
|
||
|
||
DoTask();
|
||
Wait();
|
||
}
|
||
handlers.Remove(action);
|
||
}
|
||
|
||
internal unsafe void SetFlag()
|
||
{
|
||
//lock (_lock)
|
||
//{
|
||
// if (_signaled)
|
||
// return;
|
||
// _signaled = true;
|
||
// int buf = 0;
|
||
// write(_sigwrite, &buf, new IntPtr(1));
|
||
//}
|
||
try
|
||
{
|
||
wake.Send(new byte[] { 0xFF });
|
||
}
|
||
catch (SocketException ex)
|
||
{
|
||
if (ex.SocketErrorCode != SocketError.WouldBlock)
|
||
{
|
||
throw;
|
||
}
|
||
}
|
||
}
|
||
|
||
void Invoke()
|
||
{
|
||
if (LinuxSynchronizationContext.asyncQueue.TryDequeue(out SendOrPostData postData))
|
||
{
|
||
postData.SendOrPostCallback(postData.Data);
|
||
}
|
||
if (LinuxSynchronizationContext.invokeQueue.TryDequeue(out SendOrPostData result))
|
||
{
|
||
result.SendOrPostCallback(result.Data);
|
||
result.ManualResetEvent.Set();
|
||
}
|
||
}
|
||
unsafe void OnEvent(ref XEvent xev, XEventHandler action)
|
||
{
|
||
foreach (var item in handlers.Reverse())
|
||
{
|
||
if (action != item)
|
||
{
|
||
if (item(ref xev))
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
//if (xev.type == XEventName.GenericEvent)
|
||
// fixed (void* data = &xev.GenericEventCookie)
|
||
// {
|
||
// XGetEventData(Display, data);
|
||
// }
|
||
if (windows.TryGetValue(xev.AnyEvent.window, out var window))
|
||
{
|
||
lock (XWindow.XlibLock)
|
||
{
|
||
window.OnEvent(ref xev);
|
||
}
|
||
}
|
||
else if (xev.type == XEventName.GenericEvent && LinuxPlatform.Platform.XI2 != null)
|
||
{
|
||
fixed (void* data = &xev.GenericEventCookie)
|
||
{
|
||
XGetEventData(Info.Display, data);
|
||
try
|
||
{
|
||
if (Info.XInputOpcode ==
|
||
xev.GenericEventCookie.extension)
|
||
{
|
||
//Console.WriteLine((IntPtr)ev.GenericEventCookie.data);
|
||
var ev = (XIEvent*)xev.GenericEventCookie.data;
|
||
if (windows.TryGetValue(((XIDeviceEvent*)ev)->EventWindow, out window))
|
||
{
|
||
Platform.XI2.OnEvent(ev, (X11Window)window);
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (xev.GenericEventCookie.data != null)
|
||
XFreeEventData(Info.Display, data);
|
||
}
|
||
}
|
||
}
|
||
//}
|
||
//finally
|
||
//{
|
||
// if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
|
||
// XFreeEventData(Display, &xev.GenericEventCookie);
|
||
//}
|
||
}
|
||
|
||
private static unsafe void DoTask()
|
||
{
|
||
if (LinuxSynchronizationContext.invokeQueue.Count > 0)
|
||
{
|
||
while (LinuxSynchronizationContext.invokeQueue.TryDequeue(out var result))
|
||
{
|
||
result.SendOrPostCallback(result.Data);
|
||
result.ManualResetEvent.Set();
|
||
}
|
||
}
|
||
if (LinuxSynchronizationContext.asyncQueue.Count > 0)
|
||
{
|
||
while (LinuxSynchronizationContext.asyncQueue.TryDequeue(out SendOrPostData data))
|
||
{
|
||
data.SendOrPostCallback(data.Data);
|
||
}
|
||
}
|
||
}
|
||
|
||
void UpdateParent(IntPtr chooser, IWindowImpl parentWindow)
|
||
{
|
||
var xid = ((X11Window)parentWindow).Handle;
|
||
gtk_widget_realize(chooser);
|
||
var window = gtk_widget_get_window(chooser);
|
||
var parent = GetForeignWindow(xid);
|
||
if (window != IntPtr.Zero && parent != IntPtr.Zero)
|
||
gdk_window_set_transient_for(window, parent);
|
||
}
|
||
|
||
async Task EnsureInitialized()
|
||
{
|
||
if (_initialized == null) _initialized = StartGtk();
|
||
|
||
if (!(await _initialized))
|
||
throw new Exception("Unable to initialize GTK on separate thread");
|
||
}
|
||
private Task<bool> _initialized;
|
||
private unsafe Task<string[]> ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action,
|
||
bool multiSelect, string initialFileName, IEnumerable<FileDialogFilter> filters)
|
||
{
|
||
IntPtr dlg;
|
||
using (var name = new Utf8Buffer(title))
|
||
dlg = gtk_file_chooser_dialog_new(name, IntPtr.Zero, action, IntPtr.Zero);
|
||
UpdateParent(dlg, parent);
|
||
if (multiSelect)
|
||
gtk_file_chooser_set_select_multiple(dlg, true);
|
||
|
||
gtk_window_set_modal(dlg, true);
|
||
var tcs = new TaskCompletionSource<string[]>();
|
||
List<IDisposable> disposables = null;
|
||
|
||
|
||
if (filters != null)
|
||
foreach (var f in filters.Where(a => !string.IsNullOrWhiteSpace(a.Extensions)))
|
||
{
|
||
var filter = gtk_file_filter_new();
|
||
using (var b = new Utf8Buffer(f.Name))
|
||
gtk_file_filter_set_name(filter, b);
|
||
|
||
foreach (var e in f.Extensions.Split(',').Where(a => !string.IsNullOrWhiteSpace(a)).Select(a => a.Trim()))
|
||
using (var b = new Utf8Buffer("*." + e))
|
||
gtk_file_filter_add_pattern(filter, b);
|
||
|
||
gtk_file_chooser_add_filter(dlg, filter);
|
||
}
|
||
|
||
disposables = new List<IDisposable>
|
||
{
|
||
ConnectSignal<signal_generic>(dlg, "close", delegate
|
||
{
|
||
tcs.TrySetResult(null);
|
||
foreach (var d in disposables) d.Dispose();
|
||
disposables.Clear();
|
||
return false;
|
||
}),
|
||
ConnectSignal<signal_dialog_response>(dlg, "response", (_, resp, __) =>
|
||
{
|
||
string[] result = null;
|
||
if (resp == GtkResponseType.Accept)
|
||
{
|
||
var resultList = new List<string>();
|
||
var gs = gtk_file_chooser_get_filenames(dlg);
|
||
var cgs = gs;
|
||
while (cgs != null)
|
||
{
|
||
if (cgs->Data != IntPtr.Zero)
|
||
resultList.Add(Utf8Buffer.StringFromPtr(cgs->Data));
|
||
cgs = cgs->Next;
|
||
}
|
||
g_slist_free(gs);
|
||
result = resultList.ToArray();
|
||
}
|
||
|
||
gtk_widget_hide(dlg);
|
||
foreach (var d in disposables) d.Dispose();
|
||
disposables.Clear();
|
||
tcs.TrySetResult(result);
|
||
return false;
|
||
})
|
||
};
|
||
using (var open = new Utf8Buffer(
|
||
action == GtkFileChooserAction.Save ? "Save"
|
||
: action == GtkFileChooserAction.SelectFolder ? "Select"
|
||
: "Open"))
|
||
gtk_dialog_add_button(dlg, open, GtkResponseType.Accept);
|
||
using (var open = new Utf8Buffer("Cancel"))
|
||
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel);
|
||
if (initialFileName != null)
|
||
using (var fn = new Utf8Buffer(initialFileName))
|
||
{
|
||
if (action == GtkFileChooserAction.Save)
|
||
gtk_file_chooser_set_current_name(dlg, fn);
|
||
else
|
||
gtk_file_chooser_set_filename(dlg, fn);
|
||
}
|
||
|
||
gtk_window_present(dlg);
|
||
return tcs.Task;
|
||
}
|
||
|
||
string NameWithExtension(string path, string defaultExtension, FileDialogFilter filter)
|
||
{
|
||
var name = Path.GetFileName(path);
|
||
if (name != null && !name.Contains("."))
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(filter?.Extensions))
|
||
{
|
||
if (defaultExtension != null
|
||
&& filter.Extensions.Contains(defaultExtension))
|
||
return path + "." + defaultExtension.TrimStart('.');
|
||
|
||
var ext = filter.Extensions.Split(',').FirstOrDefault(x => x != "*");
|
||
if (ext != null)
|
||
return path + "." + ext.TrimStart('.');
|
||
}
|
||
|
||
if (defaultExtension != null)
|
||
path += "." + defaultExtension.TrimStart('.');
|
||
}
|
||
|
||
return path;
|
||
}
|
||
|
||
public override async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
|
||
{
|
||
await EnsureInitialized();
|
||
return await await RunOnGlibThread(
|
||
() => ShowDialog(dialog.Title, parent,
|
||
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
|
||
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
|
||
System.IO.Path.Combine(string.IsNullOrEmpty(dialog.Directory) ? "" : dialog.Directory,
|
||
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters));
|
||
}
|
||
|
||
public override async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
|
||
{
|
||
await EnsureInitialized();
|
||
return await await RunOnGlibThread(async () =>
|
||
{
|
||
var res = await ShowDialog(dialog.Title, parent,
|
||
GtkFileChooserAction.SelectFolder, false, dialog.Directory, null);
|
||
return res?.FirstOrDefault();
|
||
});
|
||
}
|
||
|
||
public override INativeImpl CreateNative()
|
||
{
|
||
return new NativeHost();
|
||
}
|
||
|
||
public override INotifyIconImpl CreateNotifyIcon()
|
||
{
|
||
return new NotifyIcon();
|
||
}
|
||
|
||
}
|
||
/// <summary>
|
||
/// 处理事件,返回值为true的时候不调用默认处理方法
|
||
/// </summary>
|
||
/// <param name="xEvent"></param>
|
||
/// <returns></returns>
|
||
public delegate bool XEventHandler(ref XEvent xEvent);
|
||
|
||
public class CancelHandle
|
||
{
|
||
public bool Cancel { get; set; }
|
||
|
||
public object Data { get; set; }
|
||
/// <summary>
|
||
/// 设置数据,同时设置Cancel=true
|
||
/// </summary>
|
||
/// <param name="data"></param>
|
||
public void SetResult(object data)
|
||
{
|
||
Data = data;
|
||
Cancel = true;
|
||
}
|
||
}
|
||
}
|