@ -24,11 +24,12 @@ namespace Avalonia.FreeDesktop
public static string GenerateDBusMenuObjPath = > $"/net/avaloniaui/dbusmenu/{Guid.NewGuid():N}" ;
private class DBusMenuExporterImpl : ComCanonicalDbusmenu , ITopLevelNativeMenuExporter , IDisposable
private sealed class DBusMenuExporterImpl : ComCanonicalDbusmenu , ITopLevelNativeMenuExporter , IDisposable
{
private readonly Dictionary < int , NativeMenuItemBase > _ idsToItems = new ( ) ;
private readonly Dictionary < NativeMenuItemBase , int > _ itemsToIds = new ( ) ;
private readonly HashSet < NativeMenu > _ menus = new ( ) ;
private readonly HashSet < NativeMenu > _ menus = [ ] ;
private readonly PathHandler _ pathHandler ;
private readonly uint _ xid ;
private readonly bool _ appMenu = true ;
private ComCanonicalAppMenuRegistrar ? _ registrar ;
@ -40,37 +41,29 @@ namespace Avalonia.FreeDesktop
public DBusMenuExporterImpl ( Connection connection , IntPtr xid )
{
InitBackingProperties ( ) ;
Version = 4 ;
Connection = connection ;
_ xid = ( uint ) xid . ToInt32 ( ) ;
Path = GenerateDBusMenuObjPath ;
SetNativeMenu ( new NativeMenu ( ) ) ;
_ pathHandler = new PathHandler ( GenerateDBusMenuObjPath ) ;
_ pathHandler . Add ( this ) ;
SetNativeMenu ( [ ] ) ;
_ = InitializeAsync ( ) ;
}
public DBusMenuExporterImpl ( Connection connection , string path )
{
InitBackingProperties ( ) ;
Version = 4 ;
Connection = connection ;
_ appMenu = false ;
Path = path ;
SetNativeMenu ( new NativeMenu ( ) ) ;
_ pathHandler = new PathHandler ( path ) ;
_ pathHandler . Add ( this ) ;
SetNativeMenu ( [ ] ) ;
_ = InitializeAsync ( ) ;
}
private void InitBackingProperties ( )
{
BackingProperties . Version = 4 ;
BackingProperties . Status = string . Empty ;
BackingProperties . TextDirection = string . Empty ;
BackingProperties . IconThemePath = Array . Empty < string > ( ) ;
}
protected override Connection Connection { get ; }
public override Connection Connection { get ; }
public override string Path { get ; }
protected override ValueTask < ( uint revision , ( int , Dictionary < string , DBusVariantItem > , DBusVariantItem [ ] ) layout ) > OnGetLayoutAsync ( int parentId , int recursionDepth , string [ ] propertyNames )
protected override ValueTask < ( uint Revision , ( int , Dictionary < string , Variant > , Variant [ ] ) Layout ) > OnGetLayoutAsync ( int parentId , int recursionDepth , string [ ] propertyNames )
{
var menu = GetMenu ( parentId ) ;
var layout = GetLayout ( menu . item , menu . menu , recursionDepth , propertyNames ) ;
@ -80,36 +73,36 @@ namespace Avalonia.FreeDesktop
OnIsNativeMenuExportedChanged ? . Invoke ( this , EventArgs . Empty ) ;
}
return new ValueTask < ( uint , ( int , Dictionary < string , DBus VariantItem > , DBus VariantItem [ ] ) ) > ( ( _ revision , layout ) ) ;
return new ValueTask < ( uint , ( int , Dictionary < string , Variant > , Variant [ ] ) ) > ( ( _ revision , layout ) ) ;
}
protected override ValueTask < ( int , Dictionary < string , DBus VariantItem > ) [ ] > OnGetGroupPropertiesAsync ( int [ ] ids , string [ ] propertyNames )
protected override ValueTask < ( int , Dictionary < string , Variant > ) [ ] > OnGetGroupPropertiesAsync ( int [ ] ids , string [ ] propertyNames )
= > new ( ids . Select ( id = > ( id , GetProperties ( GetMenu ( id ) , propertyNames ) ) ) . ToArray ( ) ) ;
protected override ValueTask < DBus VariantItem > OnGetPropertyAsync ( int id , string name ) = >
new ( GetProperty ( GetMenu ( id ) , name ) ? ? new DBus VariantItem ( "i" , new DBusInt32Item ( 0 ) ) ) ;
protected override ValueTask < Variant > OnGetPropertyAsync ( int id , string name ) = >
new ( GetProperty ( GetMenu ( id ) , name ) ? ? new Variant ( 0 ) ) ;
protected override ValueTask OnEventAsync ( int id , string eventId , DBusVariantItem data , uint timestamp )
protected override ValueTask OnEventAsync ( int id , string eventId , VariantValue data , uint timestamp )
{
HandleEvent ( id , eventId ) ;
return new ValueTask ( ) ;
}
protected override ValueTask < int [ ] > OnEventGroupAsync ( ( int , string , DBusVariantItem , uint ) [ ] events )
protected override ValueTask < int [ ] > OnEventGroupAsync ( ( int , string , VariantValue , uint ) [ ] events )
{
foreach ( var e in events )
HandleEvent ( e . Item1 , e . Item2 ) ;
return new ValueTask < int [ ] > ( Array . Empty < int > ( ) ) ;
return new ValueTask < int [ ] > ( [ ] ) ;
}
protected override ValueTask < bool > OnAboutToShowAsync ( int id ) = > new ( false ) ;
protected override ValueTask < ( int [ ] u pdatesNeeded, int [ ] i dErrors) > OnAboutToShowGroupAsync ( int [ ] ids ) = >
new ( ( Array . Empty < int > ( ) , Array . Empty < int > ( ) ) ) ;
protected override ValueTask < ( int [ ] U pdatesNeeded, int [ ] I dErrors) > OnAboutToShowGroupAsync ( int [ ] ids ) = >
new ( ( [ ] , [ ] ) ) ;
private async Task InitializeAsync ( )
{
Connection . AddMethodHandler ( this ) ;
Connection . AddMethodHandler ( _ pathHandler ) ;
if ( ! _ appMenu )
return ;
@ -117,7 +110,7 @@ namespace Avalonia.FreeDesktop
try
{
if ( ! _d isposed )
await _ registrar . RegisterWindowAsync ( _ xid , Path ) ;
await _ registrar . RegisterWindowAsync ( _ xid , _ pathHandler . Path ) ;
}
catch
{
@ -136,17 +129,17 @@ namespace Avalonia.FreeDesktop
_d isposed = true ;
// Fire and forget
_ = _ registrar ? . UnregisterWindowAsync ( _ xid ) ;
_ pathHandler . Remove ( this ) ;
Connection . RemoveMethodHandler ( _ pathHandler . Path ) ;
}
public bool IsNativeMenuExported { get ; private set ; }
public event EventHandler ? OnIsNativeMenuExportedChanged ;
public void SetNativeMenu ( NativeMenu ? menu )
{
menu ? ? = new NativeMenu ( ) ;
menu ? ? = [ ] ;
if ( _ menu is not null )
( ( INotifyCollectionChanged ) _ menu . Items ) . CollectionChanged - = OnMenuItemsChanged ;
@ -216,39 +209,34 @@ namespace Avalonia.FreeDesktop
private void OnItemPropertyChanged ( object? sender , AvaloniaPropertyChangedEventArgs e ) = > QueueReset ( ) ;
private static readonly string [ ] s_allProperties = {
"type" , "label" , "enabled" , "visible" , "shortcut" , "toggle-type" , "children-display" , "toggle-state" , "icon-data"
} ;
private static readonly string [ ] s_allProperties = [ "type" , "label" , "enabled" , "visible" , "shortcut" , "toggle-type" , "children-display" , "toggle-state" , "icon-data" ] ;
private static DBus VariantItem ? GetProperty ( ( NativeMenuItemBase ? item , NativeMenu ? menu ) i , string name )
private static Variant ? GetProperty ( ( NativeMenuItemBase ? item , NativeMenu ? menu ) i , string name )
{
var ( it , menu ) = i ;
if ( it is NativeMenuItemSeparator )
{
if ( name = = "type" )
return new DBus VariantItem ( "s" , new DBusStringItem ( "separator" ) ) ;
return new Variant ( "separator" ) ;
}
else if ( it is NativeMenuItem item )
{
if ( name = = "type" )
return null ;
if ( name = = "label" )
return new DBus VariantItem ( "s" , new DBusStringItem ( item . Header ? ? "<null>" ) ) ;
return new Variant ( item . Header ? ? "<null>" ) ;
if ( name = = "enabled" )
{
if ( item . Menu is not null & & item . Menu . Items . Count = = 0 )
return new DBus VariantItem ( "b" , new DBusBoolItem ( false ) ) ;
return new Variant ( false ) ;
if ( ! item . IsEnabled )
return new DBus VariantItem ( "b" , new DBusBoolItem ( false ) ) ;
return new Variant ( false ) ;
return null ;
}
if ( name = = "visible" ) {
if ( ! item . IsVisible )
return new DBusVariantItem ( "b" , new DBusBoolItem ( false ) ) ;
return new DBusVariantItem ( "b" , new DBusBoolItem ( true ) ) ;
}
if ( name = = "visible" )
return new Variant ( item . IsVisible ) ;
if ( name = = "shortcut" )
{
@ -256,30 +244,30 @@ namespace Avalonia.FreeDesktop
return null ;
if ( item . Gesture . KeyModifiers = = 0 )
return null ;
var lst = new List < DBusItem > ( ) ;
var lst = new Array < string > ( ) ;
var mod = item . Gesture ;
if ( mod . KeyModifiers . HasAllFlags ( KeyModifiers . Control ) )
lst . Add ( new DBusStringItem ( "Control" ) ) ;
lst . Add ( "Control" ) ;
if ( mod . KeyModifiers . HasAllFlags ( KeyModifiers . Alt ) )
lst . Add ( new DBusStringItem ( "Alt" ) ) ;
lst . Add ( "Alt" ) ;
if ( mod . KeyModifiers . HasAllFlags ( KeyModifiers . Shift ) )
lst . Add ( new DBusStringItem ( "Shift" ) ) ;
lst . Add ( "Shift" ) ;
if ( mod . KeyModifiers . HasAllFlags ( KeyModifiers . Meta ) )
lst . Add ( new DBusStringItem ( "Super" ) ) ;
lst . Add ( new DBusStringItem ( item . Gesture . Key . ToString ( ) ) ) ;
return new DBusVariantItem ( "aas" , new DBusArrayItem ( DBusType . Array , new [ ] { new DBusArrayItem ( DBusType . String , lst ) } ) ) ;
lst . Add ( "Super" ) ;
lst . Add ( item . Gesture . Key . ToString ( ) ) ;
return Variant . FromArray ( new Array < Array < string > > ( [ lst ] ) ) ;
}
if ( name = = "toggle-type" )
{
if ( item . ToggleType = = NativeMenuItemToggleType . CheckBox )
return new DBus VariantItem ( "s" , new DBusStringItem ( "checkmark" ) ) ;
return new Variant ( "checkmark" ) ;
if ( item . ToggleType = = NativeMenuItemToggleType . Radio )
return new DBus VariantItem ( "s" , new DBusStringItem ( "radio" ) ) ;
return new Variant ( "radio" ) ;
}
if ( name = = "toggle-state" & & item . ToggleType ! = NativeMenuItemToggleType . None )
return new DBus VariantItem ( "i" , new DBusInt32Item ( item . IsChecked ? 1 : 0 ) ) ;
return new Variant ( item . IsChecked ? 1 : 0 ) ;
if ( name = = "icon-data" )
{
@ -292,57 +280,55 @@ namespace Avalonia.FreeDesktop
var icon = loader . LoadIcon ( item . Icon . PlatformImpl . Item ) ;
using var ms = new MemoryStream ( ) ;
icon . Save ( ms ) ;
return new DBusVariantItem ( "ay" , new DBusByteArrayItem ( ms . ToArray ( ) ) ) ;
return Variant . FromArray ( new Array < byte > ( ms . ToArray ( ) ) ) ;
}
}
}
if ( name = = "children-display" )
return menu is not null ? new DBusVariantItem ( "s" , new DBusStringItem ( "submenu" ) ) : null ;
{
if ( menu is not null )
return new Variant ( "submenu" ) ;
return null ;
}
}
return null ;
}
private static Dictionary < string , DBus VariantItem > GetProperties ( ( NativeMenuItemBase ? item , NativeMenu ? menu ) i , string [ ] names )
private static Dictionary < string , Variant > GetProperties ( ( NativeMenuItemBase ? item , NativeMenu ? menu ) i , string [ ] names )
{
if ( names . Length = = 0 )
names = s_allProperties ;
var properties = new Dictionary < string , DBus VariantItem > ( ) ;
var properties = new Dictionary < string , Variant > ( ) ;
foreach ( var n in names )
{
var v = GetProperty ( i , n ) ;
if ( v is not null )
properties . Add ( n , v ) ;
if ( v . HasValue )
properties . Add ( n , v . Value ) ;
}
return properties ;
}
private ( int , Dictionary < string , DBus VariantItem > , DBus VariantItem [ ] ) GetLayout ( NativeMenuItemBase ? item , NativeMenu ? menu , int depth , string [ ] propertyNames )
private ( int , Dictionary < string , Variant > , Variant [ ] ) GetLayout ( NativeMenuItemBase ? item , NativeMenu ? menu , int depth , string [ ] propertyNames )
{
var id = item is null ? 0 : GetId ( item ) ;
var props = GetProperties ( ( item , menu ) , propertyNames ) ;
var children = depth = = 0 | | menu is null ? Array . Empty < DBusVariantItem > ( ) : new DBus VariantItem [ menu . Items . Count ] ;
var children = depth = = 0 | | menu is null ? [ ] : new Variant [ menu . Items . Count ] ;
if ( menu is not null )
{
for ( var c = 0 ; c < children . Length ; c + + )
{
var ch = menu . Items [ c ] ;
var layout = GetLayout ( ch , ( ch as NativeMenuItem ) ? . Menu , depth = = - 1 ? - 1 : depth - 1 , propertyNames ) ;
children [ c ] = new DBusVariantItem ( "(ia{sv}av)" , new DBusStructItem ( new DBusItem [ ]
{
new DBusInt32Item ( layout . Item1 ) ,
new DBusArrayItem ( DBusType . DictEntry , layout . Item2 . Select ( static x = > new DBusDictEntryItem ( new DBusStringItem ( x . Key ) , x . Value ) ) . ToArray ( ) ) ,
new DBusArrayItem ( DBusType . Variant , layout . Item3 )
} ) ) ;
children [ c ] = Variant . FromStruct ( Struct . Create ( layout . Item1 , new Dict < string , Variant > ( layout . Item2 ) , new Array < Variant > ( layout . Item3 ) ) ) ;
}
}
return ( id , props , children ) ;
}
private void HandleEvent ( int id , string eventId )
{
if ( eventId = = "clicked" )