mirror of
				https://gitee.com/csharpui/CPF.git
				synced 2025-11-01 00:46:56 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			283 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Runtime.InteropServices;
 | |
| using System.Threading;
 | |
| using System.Threading.Tasks;
 | |
| using CPF.Platform;
 | |
| 
 | |
| namespace CPF.Linux
 | |
| {
 | |
| 
 | |
|     static unsafe class Glib
 | |
|     {
 | |
|         private const string GlibName = "libglib-2.0.so.0";
 | |
|         private const string GObjectName = "libgobject-2.0.so.0";
 | |
| 
 | |
|         [DllImport(GlibName)]
 | |
|         public static extern void g_slist_free(GSList* data);
 | |
| 
 | |
|         [DllImport(GObjectName)]
 | |
|         private static extern void g_object_ref(IntPtr instance);
 | |
| 
 | |
|         [DllImport(GObjectName)]
 | |
|         private static extern ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal,
 | |
|             IntPtr handler, IntPtr userData, int flags);
 | |
| 
 | |
|         [DllImport(GObjectName)]
 | |
|         private static extern void g_object_unref(IntPtr instance);
 | |
| 
 | |
|         [DllImport(GObjectName)]
 | |
|         private static extern ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId);
 | |
| 
 | |
|         private delegate bool timeout_callback(IntPtr data);
 | |
| 
 | |
|         [DllImport(GlibName)]
 | |
|         private static extern ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data,
 | |
|             IntPtr destroy);
 | |
| 
 | |
| 
 | |
|         class ConnectedSignal : IDisposable
 | |
|         {
 | |
|             private readonly IntPtr _instance;
 | |
|             private GCHandle _handle;
 | |
|             private readonly ulong _id;
 | |
| 
 | |
|             public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id)
 | |
|             {
 | |
|                 _instance = instance;
 | |
|                 g_object_ref(instance);
 | |
|                 _handle = handle;
 | |
|                 _id = id;
 | |
|             }
 | |
| 
 | |
|             public void Dispose()
 | |
|             {
 | |
|                 if (_handle.IsAllocated)
 | |
|                 {
 | |
|                     g_signal_handler_disconnect(_instance, _id);
 | |
|                     g_object_unref(_instance);
 | |
|                     _handle.Free();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public static IDisposable ConnectSignal<T>(IntPtr obj, string name, T handler)
 | |
|         {
 | |
|             var handle = GCHandle.Alloc(handler);
 | |
|             var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler);
 | |
|             using (var utf = new Utf8Buffer(name))
 | |
|             {
 | |
|                 var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0);
 | |
|                 if (id == 0)
 | |
|                     throw new ArgumentException("Unable to connect to signal " + name);
 | |
|                 return new ConnectedSignal(obj, handle, id);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         static bool TimeoutHandler(IntPtr data)
 | |
|         {
 | |
|             var handle = GCHandle.FromIntPtr(data);
 | |
|             var cb = (Func<bool>)handle.Target;
 | |
|             if (!cb())
 | |
|             {
 | |
|                 handle.Free();
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         private static readonly timeout_callback s_pinnedHandler;
 | |
| 
 | |
|         static Glib()
 | |
|         {
 | |
|             s_pinnedHandler = TimeoutHandler;
 | |
|         }
 | |
| 
 | |
|         static void AddTimeout(int priority, uint interval, Func<bool> callback)
 | |
|         {
 | |
|             var handle = GCHandle.Alloc(callback);
 | |
|             g_timeout_add_full(priority, interval, s_pinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero);
 | |
|         }
 | |
| 
 | |
|         public static Task<T> RunOnGlibThread<T>(Func<T> action)
 | |
|         {
 | |
|             var tcs = new TaskCompletionSource<T>();
 | |
|             AddTimeout(0, 0, () =>
 | |
|             {
 | |
| 
 | |
|                 try
 | |
|                 {
 | |
|                     tcs.SetResult(action());
 | |
|                 }
 | |
|                 catch (Exception e)
 | |
|                 {
 | |
|                     tcs.TrySetException(e);
 | |
|                 }
 | |
| 
 | |
|                 return false;
 | |
|             });
 | |
|             return tcs.Task;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     [StructLayout(LayoutKind.Sequential)]
 | |
|     public unsafe struct GSList
 | |
|     {
 | |
|         public readonly IntPtr Data;
 | |
|         public readonly GSList* Next;
 | |
|     }
 | |
| 
 | |
|     public enum GtkFileChooserAction
 | |
|     {
 | |
|         Open,
 | |
|         Save,
 | |
|         SelectFolder,
 | |
|     }
 | |
| 
 | |
|     // ReSharper disable UnusedMember.Global
 | |
|     public enum GtkResponseType
 | |
|     {
 | |
|         Help = -11,
 | |
|         Apply = -10,
 | |
|         No = -9,
 | |
|         Yes = -8,
 | |
|         Close = -7,
 | |
|         Cancel = -6,
 | |
|         Ok = -5,
 | |
|         DeleteEvent = -4,
 | |
|         Accept = -3,
 | |
|         Reject = -2,
 | |
|         None = -1,
 | |
|     }
 | |
|     // ReSharper restore UnusedMember.Global
 | |
| 
 | |
|     public static unsafe class Gtk
 | |
|     {
 | |
|         private static IntPtr s_display;
 | |
|         private const string GdkName = "libgdk-3.so.0";
 | |
|         private const string GtkName = "libgtk-3.so.0";
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         static extern void gtk_main_iteration();
 | |
| 
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void gtk_window_set_modal(IntPtr window, bool modal);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void gtk_window_present(IntPtr gtkWindow);
 | |
| 
 | |
| 
 | |
|         public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData);
 | |
| 
 | |
|         public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern IntPtr gtk_file_chooser_dialog_new(Utf8Buffer title, IntPtr parent,
 | |
|             GtkFileChooserAction action, IntPtr ignore);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void gtk_file_chooser_set_select_multiple(IntPtr chooser, bool allow);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void
 | |
|             gtk_dialog_add_button(IntPtr raw, Utf8Buffer button_text, GtkResponseType response_id);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern GSList* gtk_file_chooser_get_filenames(IntPtr chooser);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void gtk_file_chooser_set_current_name(IntPtr chooser, Utf8Buffer file);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern IntPtr gtk_file_chooser_get_filter(IntPtr chooser);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern IntPtr gtk_file_filter_new();
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern IntPtr gtk_file_filter_set_name(IntPtr filter, Utf8Buffer name);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern IntPtr gtk_file_filter_add_pattern(IntPtr filter, Utf8Buffer pattern);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern IntPtr gtk_file_chooser_add_filter(IntPtr chooser, IntPtr filter);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void gtk_widget_realize(IntPtr gtkWidget);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         public static extern void gtk_widget_hide(IntPtr gtkWidget);
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         static extern bool gtk_init_check(int argc, IntPtr argv);
 | |
| 
 | |
|         [DllImport(GdkName)]
 | |
|         static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
 | |
| 
 | |
|         [DllImport(GdkName)]
 | |
|         static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);
 | |
| 
 | |
|         [DllImport(GdkName)]
 | |
|         static extern IntPtr gdk_display_get_default();
 | |
| 
 | |
|         [DllImport(GtkName)]
 | |
|         static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags);
 | |
| 
 | |
|         [DllImport(GdkName)]
 | |
|         public static extern void gdk_window_set_transient_for(IntPtr window, IntPtr parent);
 | |
| 
 | |
|         public static IntPtr GetForeignWindow(IntPtr xid) => gdk_x11_window_foreign_new_for_display(s_display, xid);
 | |
| 
 | |
|         public static Task<bool> StartGtk()
 | |
|         {
 | |
|             var tcs = new TaskCompletionSource<bool>();
 | |
|             new Thread(() =>
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     using (var backends = new Utf8Buffer("x11"))
 | |
|                         gdk_set_allowed_backends(backends);
 | |
|                 }
 | |
|                 catch
 | |
|                 {
 | |
|                     //Ignore
 | |
|                 }
 | |
| 
 | |
|                 Environment.SetEnvironmentVariable("WAYLAND_DISPLAY",
 | |
|                     "/proc/fake-display-to-prevent-wayland-initialization-by-gtk3");
 | |
| 
 | |
|                 if (!gtk_init_check(0, IntPtr.Zero))
 | |
|                 {
 | |
|                     tcs.SetResult(false);
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 IntPtr app;
 | |
|                 using (var utf = new Utf8Buffer($"cpf.app.a{Guid.NewGuid():N}"))
 | |
|                     app = gtk_application_new(utf, 0);
 | |
|                 if (app == IntPtr.Zero)
 | |
|                 {
 | |
|                     tcs.SetResult(false);
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 s_display = gdk_display_get_default();
 | |
|                 tcs.SetResult(true);
 | |
|                 while (true)
 | |
|                     gtk_main_iteration();
 | |
|             })
 | |
|             { Name = "GTK3THREAD", IsBackground = true }.Start();
 | |
|             return tcs.Task;
 | |
|         }
 | |
|     }
 | |
| }
 | 
