Browse Source

X11 transparency reporting and blur support

pull/3962/head
Nikita Tsukanov 6 years ago
parent
commit
1a5c8fcf3f
  1. 83
      src/Avalonia.X11/TransparencyHelper.cs
  2. 2
      src/Avalonia.X11/X11Atoms.cs
  3. 183
      src/Avalonia.X11/X11Globals.cs
  4. 2
      src/Avalonia.X11/X11Platform.cs
  5. 15
      src/Avalonia.X11/X11Window.cs

83
src/Avalonia.X11/TransparencyHelper.cs

@ -0,0 +1,83 @@
using System;
using Avalonia.Controls;
namespace Avalonia.X11
{
class TransparencyHelper : IDisposable, X11Globals.IGlobalsSubscriber
{
private readonly X11Info _x11;
private readonly IntPtr _window;
private readonly X11Globals _globals;
private WindowTransparencyLevel _currentLevel;
private WindowTransparencyLevel _requestedLevel;
private bool _isCompositing;
private bool _blurAtomsAreSet;
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public WindowTransparencyLevel CurrentLevel => _currentLevel;
public TransparencyHelper(X11Info x11, IntPtr window, X11Globals globals)
{
_x11 = x11;
_window = window;
_globals = globals;
_globals.AddSubscriber(this);
}
public void SetTransparencyRequest(WindowTransparencyLevel level)
{
_requestedLevel = level;
UpdateTransparency();
}
private void UpdateTransparency()
{
var newLevel = UpdateAtomsAndGetTransparency();
if (newLevel != _currentLevel)
{
_currentLevel = newLevel;
TransparencyLevelChanged?.Invoke(newLevel);
}
}
private WindowTransparencyLevel UpdateAtomsAndGetTransparency()
{
if (_requestedLevel >= WindowTransparencyLevel.Blur)
{
if (!_blurAtomsAreSet)
{
IntPtr value = IntPtr.Zero;
XLib.XChangeProperty(_x11.Display, _window, _x11.Atoms._KDE_NET_WM_BLUR_BEHIND_REGION,
_x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref value, 1);
_blurAtomsAreSet = true;
}
}
else
{
if (_blurAtomsAreSet)
{
XLib.XDeleteProperty(_x11.Display, _window, _x11.Atoms._KDE_NET_WM_BLUR_BEHIND_REGION);
_blurAtomsAreSet = false;
}
}
if (!_globals.IsCompositionEnabled)
return WindowTransparencyLevel.None;
if (_requestedLevel >= WindowTransparencyLevel.Blur && CanBlur)
return WindowTransparencyLevel.Blur;
return WindowTransparencyLevel.Transparent;
}
private bool CanBlur => _globals.WmName == "KWin" && _globals.IsCompositionEnabled;
public void Dispose()
{
_globals.RemoveSubscriber(this);
}
void X11Globals.IGlobalsSubscriber.WmChanged(string wmName) => UpdateTransparency();
void X11Globals.IGlobalsSubscriber.CompositionChanged(bool compositing) => UpdateTransparency();
}
}

2
src/Avalonia.X11/X11Atoms.cs

@ -185,6 +185,8 @@ namespace Avalonia.X11
public readonly IntPtr UTF8_STRING;
public readonly IntPtr UTF16_STRING;
public readonly IntPtr ATOM_PAIR;
public readonly IntPtr MANAGER;
public readonly IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION;
public X11Atoms(IntPtr display)

183
src/Avalonia.X11/X11Globals.cs

@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
unsafe class X11Globals
{
private readonly AvaloniaX11Platform _plat;
private readonly int _screenNumber;
private readonly X11Info _x11;
private readonly IntPtr _rootWindow;
private readonly IntPtr _compositingAtom;
private readonly List<IGlobalsSubscriber> _subscribers = new List<IGlobalsSubscriber>();
private string _wmName;
private IntPtr _compositionAtomOwner;
private bool _isCompositionEnabled;
public X11Globals(AvaloniaX11Platform plat)
{
_plat = plat;
_x11 = plat.Info;
_screenNumber = XDefaultScreen(_x11.Display);
_rootWindow = XRootWindow(_x11.Display, _screenNumber);
plat.Windows[_rootWindow] = OnRootWindowEvent;
XSelectInput(_x11.Display, _rootWindow,
new IntPtr((int)(EventMask.StructureNotifyMask | EventMask.PropertyChangeMask)));
_compositingAtom = XInternAtom(_x11.Display, "_NET_WM_CM_S" + _screenNumber, false);
UpdateWmName();
UpdateCompositingAtomOwner();
}
public string WmName
{
get => _wmName;
private set
{
if (_wmName != value)
{
_wmName = value;
// The collection might change during enumeration
foreach (var s in _subscribers.ToList())
s.WmChanged(value);
}
}
}
private IntPtr CompositionAtomOwner
{
get => _compositionAtomOwner;
set
{
if (_compositionAtomOwner != value)
{
_compositionAtomOwner = value;
IsCompositionEnabled = _compositionAtomOwner != IntPtr.Zero;
}
}
}
public bool IsCompositionEnabled
{
get => _isCompositionEnabled;
set
{
if (_isCompositionEnabled != value)
{
_isCompositionEnabled = value;
// The collection might change during enumeration
foreach (var s in _subscribers.ToList())
s.CompositionChanged(value);
}
}
}
IntPtr GetSupportingWmCheck(IntPtr window)
{
XGetWindowProperty(_x11.Display, _rootWindow, _x11.Atoms._NET_SUPPORTING_WM_CHECK,
IntPtr.Zero, new IntPtr(IntPtr.Size), false,
_x11.Atoms.XA_WINDOW, out IntPtr actualType, out int actualFormat, out IntPtr nitems,
out IntPtr bytesAfter, out IntPtr prop);
if (nitems.ToInt32() != 1)
return IntPtr.Zero;
try
{
if (actualType != _x11.Atoms.XA_WINDOW)
return IntPtr.Zero;
return *(IntPtr*)prop.ToPointer();
}
finally
{
XFree(prop);
}
}
void UpdateCompositingAtomOwner()
{
// This procedure is described in https://tronche.com/gui/x/icccm/sec-2.html#s-2.8
// Check the server-side selection owner
var newOwner = XGetSelectionOwner(_x11.Display, _compositingAtom);
while (CompositionAtomOwner != newOwner)
{
// We have a new owner, unsubscribe from the previous one first
if (CompositionAtomOwner != IntPtr.Zero)
{
_plat.Windows.Remove(CompositionAtomOwner);
XSelectInput(_x11.Display, CompositionAtomOwner, IntPtr.Zero);
}
// Set it as the current owner and select input
CompositionAtomOwner = newOwner;
if (CompositionAtomOwner != IntPtr.Zero)
{
_plat.Windows[newOwner] = HandleCompositionAtomOwnerEvents;
XSelectInput(_x11.Display, CompositionAtomOwner, new IntPtr((int)(EventMask.StructureNotifyMask)));
}
// Check for the new owner again and repeat the procedure if it was changed between XGetSelectionOwner and XSelectInput call
newOwner = XGetSelectionOwner(_x11.Display, _compositingAtom);
}
}
private void HandleCompositionAtomOwnerEvents(XEvent ev)
{
if(ev.type == XEventName.DestroyNotify)
UpdateCompositingAtomOwner();
}
void UpdateWmName() => WmName = GetWmName();
string GetWmName()
{
var wm = GetSupportingWmCheck(_rootWindow);
if (wm == IntPtr.Zero || wm != GetSupportingWmCheck(wm))
return null;
XGetWindowProperty(_x11.Display, wm, _x11.Atoms._NET_WM_NAME,
IntPtr.Zero, new IntPtr(0x7fffffff),
false, _x11.Atoms.UTF8_STRING, out var actualType, out var actualFormat,
out var nitems, out _, out var prop);
if (nitems == IntPtr.Zero)
return null;
try
{
if (actualFormat != 8)
return null;
return Marshal.PtrToStringAnsi(prop, nitems.ToInt32());
}
finally
{
XFree(prop);
}
}
private void OnRootWindowEvent(XEvent ev)
{
if (ev.type == XEventName.PropertyNotify)
{
if(ev.PropertyEvent.atom == _x11.Atoms._NET_SUPPORTING_WM_CHECK)
UpdateWmName();
}
if (ev.type == XEventName.ClientMessage)
{
if(ev.ClientMessageEvent.message_type == _x11.Atoms.MANAGER
&& ev.ClientMessageEvent.ptr2 == _compositingAtom)
UpdateCompositingAtomOwner();
}
}
public interface IGlobalsSubscriber
{
void WmChanged(string wmName);
void CompositionChanged(bool compositing);
}
public void AddSubscriber(IGlobalsSubscriber subscriber) => _subscribers.Add(subscriber);
public void RemoveSubscriber(IGlobalsSubscriber subscriber) => _subscribers.Remove(subscriber);
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -26,6 +26,7 @@ namespace Avalonia.X11
public IX11Screens X11Screens { get; private set; }
public IScreenImpl Screens { get; private set; }
public X11PlatformOptions Options { get; private set; }
public X11Globals Globals { get; private set; }
public void Initialize(X11PlatformOptions options)
{
Options = options;
@ -36,6 +37,7 @@ namespace Avalonia.X11
throw new Exception("XOpenDisplay failed");
XError.Init();
Info = new X11Info(Display, DeferredDisplay);
Globals = new X11Globals(this);
//TODO: log
if (options.UseDBusMenu)
DBusHelper.TryInitialize();

15
src/Avalonia.X11/X11Window.cs

@ -43,6 +43,7 @@ namespace Avalonia.X11
private bool _wasMappedAtLeastOnce = false;
private double? _scalingOverride;
private bool _disabled;
private TransparencyHelper _transparencyHelper;
public object SyncRoot { get; } = new object();
@ -176,6 +177,9 @@ namespace Avalonia.X11
UpdateSizeHints(null);
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
_transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals);
_transparencyHelper.SetTransparencyRequest(WindowTransparencyLevel.None);
XFlush(_x11.Display);
if(_popup)
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
@ -301,7 +305,11 @@ namespace Avalonia.X11
public Func<bool> Closing { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged
{
get => _transparencyHelper.TransparencyLevelChanged;
set => _transparencyHelper.TransparencyLevelChanged = value;
}
public Action Closed { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
@ -1087,8 +1095,9 @@ namespace Avalonia.X11
public IPopupPositioner PopupPositioner { get; }
public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
_transparencyHelper.SetTransparencyRequest(transparencyLevel);
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public WindowTransparencyLevel TransparencyLevel => _transparencyHelper.CurrentLevel;
}
}

Loading…
Cancel
Save