1+ #if ! UNITY_EDITOR
2+ using System ;
3+ using System . Collections . Generic ;
4+ using System . Diagnostics ;
5+ using System . Runtime . InteropServices ;
6+ using Gtk ;
7+ using Unity . Collections ;
8+ using UnityEngine ;
9+ using X11 ;
10+ using Debug = UnityEngine . Debug ;
11+
12+ public static class EarlyEnvSet
13+ {
14+ [ DllImport ( "libc" ) ]
15+ private static extern IntPtr setenv ( string name , string value , int overwrite ) ;
16+
17+ [ DllImport ( X11Manager . LibX11 ) ]
18+ private static extern int XGetWindowAttributes ( IntPtr display , IntPtr window , out X11Manager . XWindowAttributes attributes ) ;
19+
20+ [ DllImport ( X11Manager . LibXRender ) ]
21+ private static extern IntPtr XRenderFindVisualFormat ( IntPtr display , IntPtr visual ) ;
22+
23+ [ DllImport ( X11Manager . LibX11 ) ]
24+ private static extern IntPtr XOpenDisplay ( string displayName ) ;
25+
26+ [ DllImport ( X11Manager . LibX11 ) ]
27+ private static extern IntPtr XInternAtom ( IntPtr display , string atomName , bool onlyIfExists ) ;
28+
29+ [ DllImport ( X11Manager . LibX11 ) ]
30+ private static extern IntPtr XDefaultRootWindow ( IntPtr display ) ;
31+
32+ [ DllImport ( X11Manager . LibX11 ) ]
33+ private static extern bool XTranslateCoordinates ( IntPtr display , IntPtr srcW , IntPtr destW ,
34+ int srcX , int srcY , out int destX , out int destY , out IntPtr child ) ;
35+
36+ [ DllImport ( X11Manager . LibX11 ) ]
37+ private static extern int XGetWindowProperty ( IntPtr display , IntPtr window , IntPtr property ,
38+ long longOffset , long longLength , bool delete , IntPtr reqType ,
39+ out IntPtr actualTypeReturn , out int actualFormatReturn ,
40+ out ulong nItemsReturn , out ulong bytesAfterReturn , out IntPtr propReturn ) ;
41+
42+ [ DllImport ( X11Manager . LibX11 ) ]
43+ private static extern int XGetSelectionOwner ( IntPtr display , IntPtr atom ) ;
44+
45+ [ DllImport ( X11Manager . LibX11 ) ]
46+ private static extern int XScreenCount ( IntPtr display ) ;
47+
48+ [ DllImport ( X11Manager . LibX11 ) ]
49+ private static extern int XFree ( IntPtr data ) ;
50+
51+
52+ [ RuntimeInitializeOnLoadMethod ( RuntimeInitializeLoadType . BeforeSplashScreen ) ]
53+ private static void InitBeforeAnything ( )
54+ {
55+ setenv ( "GDK_BACKEND" , "x11" , 0 ) ;
56+ setenv ( "NO_AT_BRIDGE" , "1" , 0 ) ;
57+ string [ ] argc = { } ;
58+ if ( ! Init . Check ( ref argc ) )
59+ {
60+ throw new Exception ( "Gtk initialization failed." ) ;
61+ }
62+ var display = XOpenDisplay ( null ) ;
63+ if ( display == IntPtr . Zero )
64+ {
65+ throw new Exception ( "Cannot open X11 display" ) ;
66+ }
67+ CheckVisual ( display , out IntPtr window ) ;
68+ if ( ! IsCompositionSupported ( display ) )
69+ {
70+ Gdk . Window unityWindow = GdkX11Helper . ForeignNewForDisplay ( window ) ;
71+ var dummyParent = new Window ( "" ) ;
72+ dummyParent . Realize ( ) ;
73+ dummyParent . SkipTaskbarHint = true ;
74+ dummyParent . SkipPagerHint = true ;
75+ dummyParent . Decorated = false ;
76+ dummyParent . Window . Reparent ( unityWindow , 0 , 0 ) ;
77+ var dialog = new MessageDialog ( dummyParent , DialogFlags . DestroyWithParent , MessageType . Warning , ButtonsType . Ok , false , "Composition is unavailable for this window manager." ) ;
78+ dialog . SecondaryText = "A compositor is required for MateEngine to show a transparent background.\n \n If you are running MateEngine on WMs that don't compose (like Openbox), try installing a compositing manager (such as picom) and configure it correctly, or simply switch to Mutter (GNOME), Xfwm4 (Xfce4) and other WMs which natively supports composition.\n \n If you are running KWin (KDE), please make sure \" Allow applications to block compositing\" is turned off in KDE System Settings (It's in the compositor section of Display and Monitor)." ;
79+ dialog . MessageType = MessageType . Warning ;
80+ var image = new Image ( new Gdk . Pixbuf ( Resources . Load < Texture2D > ( "KWinHint" ) . EncodeToPNG ( ) ) ) ;
81+ image . Halign = Align . Center ;
82+ image . Show ( ) ;
83+ dialog . ContentArea ? . PackStart ( image , false , false , 0 ) ;
84+ dialog . ShowAll ( ) ;
85+ dialog . Run ( ) ;
86+ }
87+ }
88+
89+ private static bool IsCompositionSupported ( IntPtr display )
90+ {
91+ for ( int screen = 0 ; screen < XScreenCount ( display ) ; screen ++ )
92+ {
93+ IntPtr selectionAtom = XInternAtom ( display , "_NET_WM_CM_S" + screen , false ) ;
94+ if ( selectionAtom == IntPtr . Zero )
95+ continue ;
96+ return XGetSelectionOwner ( display , selectionAtom ) != 0 ;
97+ }
98+ return false ;
99+ }
100+
101+ static void CheckVisual ( IntPtr display , out IntPtr window )
102+ {
103+ int pid = Process . GetCurrentProcess ( ) . Id ;
104+ List < IntPtr > windows = FindWindowsByPid ( display , pid ) ;
105+
106+ IntPtr unityWindow = IntPtr . Zero ;
107+
108+ if ( windows . Count > 0 )
109+ {
110+ unityWindow = windows [ 0 ] ; // Typically the first is the main window
111+ }
112+ window = unityWindow ;
113+ if ( XGetWindowAttributes ( display , unityWindow , out X11Manager . XWindowAttributes attrs ) == 0 )
114+ {
115+ Debug . LogError ( "Failed to get window attributes" ) ;
116+ return ;
117+ }
118+
119+ if ( attrs . depth != 32 || ! IsArgbVisual ( display , attrs . visual ) )
120+ {
121+ Gdk . Window gdkWindow = GdkX11Helper . ForeignNewForDisplay ( unityWindow ) ;
122+ var dummyParent = new Window ( "" ) ;
123+ dummyParent . Realize ( ) ;
124+ dummyParent . SkipTaskbarHint = true ;
125+ dummyParent . SkipPagerHint = true ;
126+ dummyParent . Decorated = false ;
127+ dummyParent . Window . Reparent ( gdkWindow , 0 , 0 ) ;
128+ var dialog = new MessageDialog ( dummyParent , DialogFlags . DestroyWithParent , MessageType . Warning , ButtonsType . Ok , false , "It looks like MateEngine is not running under an ARGB visual." ) ;
129+ dialog . SecondaryText = "MateEngine must rely on a true transparent canvas (aka ARGB visual) to show a transparent background. Without it, your avatar will display on a big black background.\n \n To acquire an ARGB visual, try launching MateEngine using the launch script (usually called launch.sh) provided in MateEngine executable path." ;
130+ dialog . MessageType = MessageType . Warning ;
131+ dialog . Run ( ) ;
132+ }
133+ }
134+
135+ private static bool IsArgbVisual ( IntPtr display , IntPtr visual )
136+ {
137+ IntPtr formatPtr = XRenderFindVisualFormat ( display , visual ) ;
138+ if ( formatPtr == IntPtr . Zero ) return false ;
139+
140+ X11Manager . XRenderPictFormat format = Marshal . PtrToStructure < X11Manager . XRenderPictFormat > ( formatPtr ) ;
141+ return format . type == 1 && format . direct . alphaMask != 0 ;
142+ }
143+
144+ private static List < IntPtr > FindWindowsByPid ( IntPtr display , int targetPid )
145+ {
146+ var result = new List < IntPtr > ( ) ;
147+ var atom = XInternAtom ( display , "_NET_WM_PID" , false ) ;
148+
149+ if ( atom == IntPtr . Zero )
150+ {
151+ Debug . Log ( "_NET_WM_PID atom not found" ) ;
152+ return result ;
153+ }
154+
155+ var windows = GetAllVisibleWindows ( display , XDefaultRootWindow ( display ) ) ;
156+
157+ foreach ( var window in windows )
158+ {
159+ int pid = GetWindowPid ( display , window , atom ) ;
160+ if ( pid == targetPid )
161+ {
162+ result . Add ( window ) ;
163+ }
164+ }
165+
166+ return result ;
167+ }
168+
169+ private static int GetWindowPid ( IntPtr display , IntPtr window , IntPtr pidAtom )
170+ {
171+ int status = XGetWindowProperty ( display , window , pidAtom ,
172+ 0 , 1 , false , ( IntPtr ) 6 ,
173+ out _ , out _ ,
174+ out var nItems , out _ , out var prop ) ;
175+
176+ if ( status == 0 && prop != IntPtr . Zero && nItems > 0 )
177+ {
178+ int pid = Marshal . ReadInt32 ( prop ) ;
179+ XFree ( prop ) ;
180+ return pid ;
181+ }
182+
183+ return - 1 ;
184+ }
185+
186+ private static bool IsWindowVisible ( IntPtr display , IntPtr rootWindow , IntPtr window )
187+ {
188+ if ( display == IntPtr . Zero ) return false ;
189+
190+ int result = XGetWindowAttributes ( display , window , out X11Manager . XWindowAttributes attr ) ;
191+ if ( result == 0 || attr . map_state != 2 ) return false ;
192+
193+ if ( ! XTranslateCoordinates ( display , window , rootWindow , 0 , 0 , out int absX , out int absY , out _ ) )
194+ return false ;
195+
196+ float targetX1 = absX ;
197+ float targetY1 = absY ;
198+ float targetX2 = absX + attr . width ;
199+ float targetY2 = absY + attr . height ;
200+ float targetArea = ( float ) attr . width * attr . height ;
201+
202+ List < IntPtr > stacking = GetClientStackingList ( display , rootWindow ) ;
203+ int index = stacking . IndexOf ( window ) ;
204+ if ( index < 0 ) return false ;
205+
206+ List < ( float x1 , float y1 , float x2 , float y2 ) > coversList = new ( ) ;
207+ for ( int i = index + 1 ; i < stacking . Count ; i ++ )
208+ {
209+ IntPtr ow = stacking [ i ] ;
210+ XGetWindowAttributes ( display , ow , out X11Manager . XWindowAttributes oattr ) ;
211+ if ( oattr . map_state != 2 ) continue ;
212+
213+ if ( ! XTranslateCoordinates ( display , ow , rootWindow , 0 , 0 , out int oabsX , out int oabsY , out _ ) )
214+ continue ;
215+
216+ float ox1 = oabsX ;
217+ float oy1 = oabsY ;
218+ float ox2 = oabsX + oattr . width ;
219+ float oy2 = oabsY + oattr . height ;
220+
221+ float ix1 = Math . Max ( targetX1 , ox1 ) ;
222+ float iy1 = Math . Max ( targetY1 , oy1 ) ;
223+ float ix2 = Math . Min ( targetX2 , ox2 ) ;
224+ float iy2 = Math . Min ( targetY2 , oy2 ) ;
225+ if ( ix1 < ix2 && iy1 < iy2 )
226+ {
227+ coversList . Add ( ( ix1 , iy1 , ix2 , iy2 ) ) ;
228+ }
229+ }
230+
231+ if ( coversList . Count == 0 ) return true ;
232+
233+ // Burst-optimized union area computation
234+ NativeArray < X11Manager . RectF > covers = new NativeArray < X11Manager . RectF > ( coversList . Count , Allocator . Temp ) ;
235+ for ( int i = 0 ; i < coversList . Count ; i ++ )
236+ {
237+ var t = coversList [ i ] ;
238+ covers [ i ] = new X11Manager . RectF { x1 = t . x1 , y1 = t . y1 , x2 = t . x2 , y2 = t . y2 } ;
239+ }
240+ float coveredArea = X11Manager . UnionAreaCalculator . Compute ( covers ) ;
241+ covers . Dispose ( ) ;
242+
243+ return coveredArea < targetArea - 1e-4f ;
244+ }
245+
246+ private static List < IntPtr > GetClientStackingList ( IntPtr display , IntPtr rootWindow )
247+ {
248+ IntPtr atom = XInternAtom ( display , "_NET_CLIENT_LIST_STACKING" , false ) ;
249+ if ( atom == IntPtr . Zero ) return new List < IntPtr > ( ) ;
250+
251+ int status = XGetWindowProperty ( display , rootWindow , atom , 0 , 1024 , false , ( IntPtr ) 33 ,
252+ out _ , out int actualFormat , out ulong nItems , out _ , out IntPtr prop ) ;
253+
254+ if ( status != 0 || prop == IntPtr . Zero || nItems == 0 || actualFormat != 32 )
255+ {
256+ if ( prop != IntPtr . Zero ) XFree ( prop ) ;
257+ return new List < IntPtr > ( ) ;
258+ }
259+
260+ List < IntPtr > windows = new List < IntPtr > ( ( int ) nItems ) ;
261+ for ( int i = 0 ; i < ( int ) nItems ; i ++ )
262+ {
263+ IntPtr w = Marshal . ReadIntPtr ( prop , ( i * IntPtr . Size ) ) ;
264+ windows . Add ( w ) ;
265+ }
266+ XFree ( prop ) ;
267+ return windows ;
268+ }
269+
270+ private static List < IntPtr > GetAllVisibleWindows ( IntPtr display , IntPtr rootWindow )
271+ {
272+ var result = new List < IntPtr > ( ) ;
273+
274+ IntPtr clientListAtom = XInternAtom ( display , "_NET_CLIENT_LIST" , true ) ;
275+ if ( clientListAtom != IntPtr . Zero )
276+ {
277+ int status = XGetWindowProperty ( display , rootWindow , clientListAtom , 0 , 1024 , false , ( IntPtr ) 33 ,
278+ out IntPtr actualType , out int actualFormat , out ulong nItems , out _ , out IntPtr prop ) ;
279+
280+ if ( status == 0 && actualType == ( IntPtr ) 33 && actualFormat == 32 && prop != IntPtr . Zero )
281+ {
282+ for ( ulong i = 0 ; i < nItems ; i ++ )
283+ {
284+ IntPtr win = Marshal . ReadIntPtr ( prop , ( int ) ( i * ( ulong ) IntPtr . Size ) ) ;
285+ if ( IsWindowVisible ( display , rootWindow , win ) )
286+ {
287+ result . Add ( win ) ;
288+ }
289+ }
290+ XFree ( prop ) ;
291+ return result ;
292+ }
293+ if ( prop != IntPtr . Zero ) XFree ( prop ) ;
294+ }
295+ return result ;
296+ }
297+ }
298+ #endif
0 commit comments