unoplatform / uno

Open-source platform for building cross-platform native Mobile, Web, Desktop and Embedded apps quickly. Create rich, C#/XAML, single-codebase apps from any IDE. Hot Reload included! 90m+ NuGet Downloads!!
https://platform.uno
Apache License 2.0
9k stars 733 forks source link

[X11] Setting window background to Transparent #16897

Closed lindexi closed 5 months ago

lindexi commented 5 months ago

What would you like to be added

I hope to be able to set the window background to transparent when UNO is based on the X11 framework.

Why is this needed

Often, developers who want to use the UNO framework to develop desktop applications on Linux aim to create a desktop component application, such as a round window for a weather display application that floats on the desktop. However, they encounter difficulties as they find it impossible to set the window background to transparent on UNO. This prevents them from fulfilling such requirements.

For which platform

Skia (Linux X11)

Anything else we need to know?

It may be a bug. Because the X11XamlRootHost will create the X11 window with White background and do not read the background property.

https://github.com/unoplatform/uno/blob/36d6a2caa6731d032fe33de19993e6d18e878c2a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs#L319-L329

https://github.com/unoplatform/uno/blob/36d6a2caa6731d032fe33de19993e6d18e878c2a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs#L400-L401

Morely, using XLib.XCreateSimpleWindow will cause X11SoftwareRenderer should use the 24 ColorDepth:

https://github.com/unoplatform/uno/blob/36d6a2caa6731d032fe33de19993e6d18e878c2a/src/Uno.UI.Runtime.Skia.X11/X11SoftwareRenderer.cs#L11

Which will lost the alpha channel. As the todo says, make sure this works everywhere.,

https://github.com/unoplatform/uno/blob/36d6a2caa6731d032fe33de19993e6d18e878c2a/src/Uno.UI.Runtime.Skia.X11/X11SoftwareRenderer.cs#L39-L40

no, it will break when we replace the XLib.XCreateSimpleWindow with this code, then it will boom:

            XLib.XMatchVisualInfo(display, screen, 32, 4, out var info);
            var visual = info.visual;
            Console.WriteLine($"info.depth={info.depth}"); // Output: info.depth=32

            var rootWindow = XLib.XRootWindow(display, screen);
            var xSetWindowAttributes = new XSetWindowAttributes()
            {
                backing_store = 1,
                bit_gravity = Gravity.NorthWestGravity,
                win_gravity = Gravity.NorthWestGravity,
                //override_redirect = true,
                colormap = XLib.XCreateColormap(display, rootWindow, visual, /* AllocNone */ 0),
                border_pixel = 0,
                background_pixel = IntPtr.Zero,
            };

            var valueMask =
                    //SetWindowValuemask.BackPixmap
                    0
                    | SetWindowValuemask.BackPixel
                    | SetWindowValuemask.BorderPixel
                    | SetWindowValuemask.BitGravity
                    | SetWindowValuemask.WinGravity
                    | SetWindowValuemask.BackingStore
                    | SetWindowValuemask.ColorMap
                //| SetWindowValuemask.OverrideRedirect
                ;

            window = XLib.XCreateWindow(display, rootWindow, 0, 0, (int)size.Width,
                (int)size.Height, 5, 32, /* InputOutput */ 1, visual,
                (UIntPtr)(valueMask), ref xSetWindowAttributes);

            //window = XLib.XCreateSimpleWindow(
            //  display,
            //  XLib.XRootWindow(display, screen),
            //  0,
            //  0,
            //  (int)size.Width,
            //  (int)size.Height,
            //  0,
            //  XLib.XBlackPixel(display, screen),
            //  IntPtr.Zero);

To be safe, the X11SoftwareRenderer should update the _xImage code as:

            if (_xImage is null)
            {
                const int bytePerPixelCount = 4; // The number of RGBA is 4 byte count
                var bitPerByte = 8;

                var bitmapWidth = width;
                var bitmapHeight = height;

                var img = new XImage();
                int bitsPerPixel = bytePerPixelCount * bitPerByte;
                img.width = bitmapWidth;
                img.height = bitmapHeight;
                img.format = 2; //ZPixmap;
                img.data = _bitmap.GetPixels();
                img.byte_order = 0; // LSBFirst;
                img.bitmap_unit = bitsPerPixel;
                img.bitmap_bit_order = 0; // LSBFirst;
                img.bitmap_pad = bitsPerPixel;
                img.depth = bitsPerPixel;
                img.bytes_per_line = bitmapWidth * bytePerPixelCount;
                img.bits_per_pixel = bitsPerPixel;
                _ = X11Helper.XInitImage(ref img);

                _xImage = img;
            }

You can find my code in https://github.com/lindexi/uno/blob/3bb774cd061e41283ed2d1eaf44b394da610c588/src/Uno.UI.Runtime.Skia.X11/X11SoftwareRenderer.cs

lindexi commented 5 months ago

I want to change the X11 window's background as Transparent as the Avalonia do. Can I do that?

Avalonia will get the 32 depth TransparentVisualInfo in https://github.com/AvaloniaUI/Avalonia/blob/0063a5e8a3760361bee8a8540d7cad5866009d71/src/Avalonia.X11/X11Info.cs#L81-L83

And then Avalonia will use TransparentVisualInfo to create the x11 window in https://github.com/AvaloniaUI/Avalonia/blob/0063a5e8a3760361bee8a8540d7cad5866009d71/src/Avalonia.X11/X11Window.cs#L111-L152

lindexi commented 5 months ago

Answer for TODO: make sure this works everywhere. AFAICT everyone is using 24 bit color with 32 bit_pad,

https://github.com/unoplatform/uno/blob/36d6a2caa6731d032fe33de19993e6d18e878c2a/src/Uno.UI.Runtime.Skia.X11/X11SoftwareRenderer.cs#L39-L40

The X11SoftwareRenderer is utilized when software rendering is in operation. The corresponding X11 window is instantiated using the following code via XLib.XCreateSimpleWindow.

https://github.com/unoplatform/uno/blob/36d6a2caa6731d032fe33de19993e6d18e878c2a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs#L319-L330

By examining the CrWindow.c code in libX11, we can understand that the implementation of XCreateSimpleWindow involves passing the CopyFromParent parameter. See

https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/9399caf2c12cbe1ed56f4f6b368c5811cb5d0458/src/CrWindow.c

In the case of UNO, the parameter is passed the value of XRootWindow, see https://github.com/unoplatform/uno/blob/36d6a2caa6731d032fe33de19993e6d18e878c2a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs#L321

Using the following code to test, I found that all my devices output RootWindowDepth=24:

var display = XOpenDisplay(IntPtr.Zero);
var screen = XDefaultScreen(display);

var rootWindow = XRootWindow(display, screen);
var rootWindowWindowAttributes = new XWindowAttributes();
XGetWindowAttributes(display, rootWindow, ref rootWindowWindowAttributes);
Console.WriteLine($"RootWindowDepth={rootWindowWindowAttributes.depth}");

This means that depth of all my devices create X11 window by XCreateSimpleWindow is 24.

And the 24 ColorDepth will work well in all my devices. But as ajax says, it can return more than 24, see https://stackoverflow.com/a/6099890

The hard-coded code specifying that the depth must be equal to 24 cannot be located in libX11 and xserver.

This means that X11SoftwareRenderer may indeed not work as expected on some devices, although such devices are rare.