committed by
GitHub
99 changed files with 3934 additions and 1432 deletions
@ -1,32 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] |
|||
namespace Avalonia.FreeDesktop |
|||
{ |
|||
[DBusInterface("org.freedesktop.portal.FileChooser")] |
|||
internal interface IFileChooser : IDBusObject |
|||
{ |
|||
Task<ObjectPath> OpenFileAsync(string ParentWindow, string Title, IDictionary<string, object> Options); |
|||
Task<ObjectPath> SaveFileAsync(string ParentWindow, string Title, IDictionary<string, object> Options); |
|||
Task<ObjectPath> SaveFilesAsync(string ParentWindow, string Title, IDictionary<string, object> Options); |
|||
Task<T> GetAsync<T>(string prop); |
|||
Task<FileChooserProperties> GetAllAsync(); |
|||
Task SetAsync(string prop, object val); |
|||
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler); |
|||
} |
|||
|
|||
[Dictionary] |
|||
internal class FileChooserProperties |
|||
{ |
|||
public uint Version { get; set; } |
|||
} |
|||
|
|||
internal static class FileChooserExtensions |
|||
{ |
|||
public static Task<uint> GetVersionAsync(this IFileChooser o) => o.GetAsync<uint>("version"); |
|||
} |
|||
} |
|||
@ -1,69 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] |
|||
namespace Avalonia.FreeDesktop.DBusIme.Fcitx |
|||
{ |
|||
[DBusInterface("org.fcitx.Fcitx.InputMethod")] |
|||
interface IFcitxInputMethod : IDBusObject |
|||
{ |
|||
Task<(int icid, bool enable, uint keyval1, uint state1, uint keyval2, uint state2)> CreateICv3Async( |
|||
string Appname, int Pid); |
|||
} |
|||
|
|||
|
|||
[DBusInterface("org.fcitx.Fcitx.InputContext")] |
|||
interface IFcitxInputContext : IDBusObject |
|||
{ |
|||
Task EnableICAsync(); |
|||
Task CloseICAsync(); |
|||
Task FocusInAsync(); |
|||
Task FocusOutAsync(); |
|||
Task ResetAsync(); |
|||
Task MouseEventAsync(int X); |
|||
Task SetCursorLocationAsync(int X, int Y); |
|||
Task SetCursorRectAsync(int X, int Y, int W, int H); |
|||
Task SetCapacityAsync(uint Caps); |
|||
Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor); |
|||
Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor); |
|||
Task DestroyICAsync(); |
|||
Task<int> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time); |
|||
Task<IDisposable> WatchEnableIMAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchCloseIMAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable?> WatchCommitStringAsync(Action<string> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable?> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception>? onError = null); |
|||
} |
|||
|
|||
[DBusInterface("org.fcitx.Fcitx.InputContext1")] |
|||
interface IFcitxInputContext1 : IDBusObject |
|||
{ |
|||
Task FocusInAsync(); |
|||
Task FocusOutAsync(); |
|||
Task ResetAsync(); |
|||
Task SetCursorRectAsync(int X, int Y, int W, int H); |
|||
Task SetCapabilityAsync(ulong Caps); |
|||
Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor); |
|||
Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor); |
|||
Task DestroyICAsync(); |
|||
Task<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time); |
|||
Task<IDisposable?> WatchCommitStringAsync(Action<string> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable?> WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception>? onError = null); |
|||
} |
|||
|
|||
[DBusInterface("org.fcitx.Fcitx.InputMethod1")] |
|||
interface IFcitxInputMethod1 : IDBusObject |
|||
{ |
|||
Task<(ObjectPath path, byte[] data)> CreateInputContextAsync((string, string)[] arg0); |
|||
} |
|||
} |
|||
@ -1,52 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] |
|||
namespace Avalonia.FreeDesktop.DBusIme.IBus |
|||
{ |
|||
[DBusInterface("org.freedesktop.IBus.InputContext")] |
|||
interface IIBusInputContext : IDBusObject |
|||
{ |
|||
Task<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State); |
|||
Task SetCursorLocationAsync(int X, int Y, int W, int H); |
|||
Task FocusInAsync(); |
|||
Task FocusOutAsync(); |
|||
Task ResetAsync(); |
|||
Task SetCapabilitiesAsync(uint Caps); |
|||
Task PropertyActivateAsync(string Name, int State); |
|||
Task SetEngineAsync(string Name); |
|||
Task<object> GetEngineAsync(); |
|||
Task DestroyAsync(); |
|||
Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos); |
|||
Task<IDisposable> WatchCommitTextAsync(Action<object> cb, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchRequireSurroundingTextAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchShowPreeditTextAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchHidePreeditTextAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchShowAuxiliaryTextAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchHideAuxiliaryTextAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchShowLookupTableAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchHideLookupTableAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchPageUpLookupTableAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchPageDownLookupTableAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchCursorUpLookupTableAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchCursorDownLookupTableAsync(Action handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchRegisterPropertiesAsync(Action<object> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchUpdatePropertyAsync(Action<object> handler, Action<Exception>? onError = null); |
|||
} |
|||
|
|||
|
|||
[DBusInterface("org.freedesktop.IBus.Portal")] |
|||
interface IIBusPortal : IDBusObject |
|||
{ |
|||
Task<ObjectPath> CreateInputContextAsync(string Name); |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] |
|||
namespace Avalonia.FreeDesktop.DBusMenu |
|||
{ |
|||
|
|||
[DBusInterface("org.freedesktop.DBus.Properties")] |
|||
interface IFreeDesktopDBusProperties : IDBusObject |
|||
{ |
|||
Task<object> GetAsync(string prop); |
|||
Task<DBusMenuProperties> GetAllAsync(); |
|||
Task SetAsync(string prop, object val); |
|||
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler); |
|||
} |
|||
|
|||
[DBusInterface("com.canonical.dbusmenu")] |
|||
interface IDBusMenu : IFreeDesktopDBusProperties |
|||
{ |
|||
Task<(uint revision, (int, KeyValuePair<string, object>[], object[]) layout)> GetLayoutAsync(int ParentId, int RecursionDepth, string[] PropertyNames); |
|||
Task<(int, KeyValuePair<string, object>[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames); |
|||
Task<object> GetPropertyAsync(int Id, string Name); |
|||
Task EventAsync(int Id, string EventId, object Data, uint Timestamp); |
|||
Task<int[]> EventGroupAsync((int id, string eventId, object data, uint timestamp)[] events); |
|||
Task<bool> AboutToShowAsync(int Id); |
|||
Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids); |
|||
Task<IDisposable> WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception>? onError = null); |
|||
} |
|||
|
|||
[Dictionary] |
|||
class DBusMenuProperties |
|||
{ |
|||
public uint Version { get; set; } = default; |
|||
public string? TextDirection { get; set; } = default; |
|||
public string? Status { get; set; } = default; |
|||
public string[]? IconThemePath { get; set; } = default; |
|||
} |
|||
|
|||
|
|||
[DBusInterface("com.canonical.AppMenu.Registrar")] |
|||
interface IRegistrar : IDBusObject |
|||
{ |
|||
Task RegisterWindowAsync(uint WindowId, ObjectPath MenuObjectPath); |
|||
Task UnregisterWindowAsync(uint WindowId); |
|||
Task<(string service, ObjectPath menuObjectPath)> GetMenuForWindowAsync(uint WindowId); |
|||
Task<(uint, string, ObjectPath)[]> GetMenusAsync(); |
|||
Task<IDisposable> WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action<Exception>? onError = null); |
|||
Task<IDisposable> WatchWindowUnregisteredAsync(Action<uint> handler, Action<Exception>? onError = null); |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] |
|||
namespace Avalonia.FreeDesktop |
|||
{ |
|||
[DBusInterface("org.freedesktop.portal.Request")] |
|||
internal interface IRequest : IDBusObject |
|||
{ |
|||
Task CloseAsync(); |
|||
Task<IDisposable> WatchResponseAsync(Action<(uint response, IDictionary<string, object> results)> handler, Action<Exception>? onError = null); |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
namespace Avalonia.FreeDesktop; |
|||
|
|||
[DBusInterface("org.freedesktop.portal.Settings")] |
|||
internal interface IDBusSettings : IDBusObject |
|||
{ |
|||
Task<(string @namespace, IDictionary<string, object>)> ReadAllAsync(string[] namespaces); |
|||
|
|||
Task<object> ReadAsync(string @namespace, string key); |
|||
|
|||
Task<IDisposable> WatchSettingChangedAsync(Action<(string @namespace, string key, object value)> handler, Action<Exception>? onError = null); |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
<node> |
|||
<interface name="org.freedesktop.DBus"> |
|||
<method name="Hello"> |
|||
<arg type="s" direction="out"></arg> |
|||
</method> |
|||
<method name="RequestName"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="u" direction="in"></arg> |
|||
<arg type="u" direction="out"></arg> |
|||
</method> |
|||
<method name="ReleaseName"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="u" direction="out"></arg> |
|||
</method> |
|||
<method name="StartServiceByName"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="u" direction="in"></arg> |
|||
<arg type="u" direction="out"></arg> |
|||
</method> |
|||
<method name="UpdateActivationEnvironment"> |
|||
<arg type="a{ss}" direction="in"></arg> |
|||
</method> |
|||
<method name="NameHasOwner"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="b" direction="out"></arg> |
|||
</method> |
|||
<method name="ListNames"> |
|||
<arg type="as" direction="out"></arg> |
|||
</method> |
|||
<method name="ListActivatableNames"> |
|||
<arg type="as" direction="out"></arg> |
|||
</method> |
|||
<method name="AddMatch"> |
|||
<arg type="s" direction="in"></arg> |
|||
</method> |
|||
<method name="RemoveMatch"> |
|||
<arg type="s" direction="in"></arg> |
|||
</method> |
|||
<method name="GetNameOwner"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="s" direction="out"></arg> |
|||
</method> |
|||
<method name="ListQueuedOwners"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="as" direction="out"></arg> |
|||
</method> |
|||
<method name="GetConnectionUnixUser"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="u" direction="out"></arg> |
|||
</method> |
|||
<method name="GetConnectionUnixProcessID"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="u" direction="out"></arg> |
|||
</method> |
|||
<method name="GetAdtAuditSessionData"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="ay" direction="out"></arg> |
|||
</method> |
|||
<method name="GetConnectionSELinuxSecurityContext"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="ay" direction="out"></arg> |
|||
</method> |
|||
<method name="ReloadConfig"></method> |
|||
<method name="GetId"> |
|||
<arg type="s" direction="out"></arg> |
|||
</method> |
|||
<method name="GetConnectionCredentials"> |
|||
<arg type="s" direction="in"></arg> |
|||
<arg type="a{sv}" direction="out"></arg> |
|||
</method> |
|||
<signal name="NameOwnerChanged"> |
|||
<arg type="s"></arg> |
|||
<arg type="s"></arg> |
|||
<arg type="s"></arg> |
|||
</signal> |
|||
<signal name="NameLost"> |
|||
<arg type="s"></arg> |
|||
</signal> |
|||
<signal name="NameAcquired"> |
|||
<arg type="s"></arg> |
|||
</signal> |
|||
<property name="Features" type="as" access="read"> |
|||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"></annotation> |
|||
</property> |
|||
<property name="Interfaces" type="as" access="read"> |
|||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"></annotation> |
|||
</property> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,437 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!-- |
|||
A library to allow applications to provide simple indications of |
|||
information to be displayed to users of the application through the |
|||
interface shell. |
|||
|
|||
Copyright 2009 Canonical Ltd. |
|||
|
|||
Authors: |
|||
Ted Gould <ted@canonical.com> |
|||
Aurélien Gâteau <aurelien.gateau@canonical.com> |
|||
|
|||
This program is free software: you can redistribute it and/or modify it |
|||
under the terms of either or both of the following licenses: |
|||
|
|||
1) the GNU Lesser General Public License version 3, as published by the |
|||
Free Software Foundation; and/or |
|||
2) the GNU Lesser General Public License version 2.1, as published by |
|||
the Free Software Foundation. |
|||
|
|||
This program is distributed in the hope that it will be useful, but |
|||
WITHOUT ANY WARRANTY; without even the implied warranties of |
|||
MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
|||
PURPOSE. See the applicable version of the GNU Lesser General Public |
|||
License for more details. |
|||
|
|||
You should have received a copy of both the GNU Lesser General Public |
|||
License version 3 and version 2.1 along with this program. If not, see |
|||
<http://www.gnu.org/licenses/> |
|||
--> |
|||
<node name="/" xmlns:dox="http://www.canonical.com/dbus/dox.dtd"> |
|||
<dox:d><![CDATA[ |
|||
@mainpage |
|||
|
|||
The goal of DBusMenu is to expose menus on DBus. |
|||
|
|||
Main interface is documented here: @ref com::canonical::dbusmenu |
|||
]]></dox:d> |
|||
<interface name="com.canonical.dbusmenu"> |
|||
<dox:d><![CDATA[ |
|||
A DBus interface to expose menus on DBus. |
|||
|
|||
Menu items are represented with a unique numeric id and a dictionary of |
|||
properties. |
|||
|
|||
To reduce the amount of DBus traffic, a property should only be returned |
|||
if its value is not the default value. |
|||
|
|||
Available properties are: |
|||
|
|||
<table> |
|||
<tr> |
|||
<th>Name</th> |
|||
<th>Type</th> |
|||
<th>Description</th> |
|||
<th>Default Value</th> |
|||
</tr> |
|||
<tr> |
|||
<td>type</td> |
|||
<td>String</td> |
|||
<td>Can be one of: |
|||
- "standard": an item which can be clicked to trigger an action or |
|||
show another menu |
|||
- "separator": a separator |
|||
|
|||
Vendor specific types can be added by prefixing them with |
|||
"x-<vendor>-". |
|||
</td> |
|||
<td>"standard"</td> |
|||
</tr> |
|||
<tr> |
|||
<td>label</td> |
|||
<td>string</td> |
|||
<td>Text of the item, except that: |
|||
-# two consecutive underscore characters "__" are displayed as a |
|||
single underscore, |
|||
-# any remaining underscore characters are not displayed at all, |
|||
-# the first of those remaining underscore characters (unless it is |
|||
the last character in the string) indicates that the following |
|||
character is the access key. |
|||
</td> |
|||
<td>""</td> |
|||
</tr> |
|||
<tr> |
|||
<td>enabled</td> |
|||
<td>boolean</td> |
|||
<td>Whether the item can be activated or not.</td> |
|||
<td>true</td> |
|||
</tr> |
|||
<tr> |
|||
<td>visible</td> |
|||
<td>boolean</td> |
|||
<td>True if the item is visible in the menu.</td> |
|||
<td>true</td> |
|||
</tr> |
|||
<tr> |
|||
<td>icon-name</td> |
|||
<td>string</td> |
|||
<td>Icon name of the item, following the freedesktop.org icon spec.</td> |
|||
<td>""</td> |
|||
</tr> |
|||
<tr> |
|||
<td>icon-data</td> |
|||
<td>binary</td> |
|||
<td>PNG data of the icon.</td> |
|||
<td>Empty</td> |
|||
</tr> |
|||
<tr> |
|||
<td>shortcut</td> |
|||
<td>array of arrays of strings</td> |
|||
<td>The shortcut of the item. Each array represents the key press |
|||
in the list of keypresses. Each list of strings contains a list of |
|||
modifiers and then the key that is used. The modifier strings |
|||
allowed are: "Control", "Alt", "Shift" and "Super". |
|||
|
|||
- A simple shortcut like Ctrl+S is represented as: |
|||
[["Control", "S"]] |
|||
- A complex shortcut like Ctrl+Q, Alt+X is represented as: |
|||
[["Control", "Q"], ["Alt", "X"]]</td> |
|||
<td>Empty</td> |
|||
</tr> |
|||
<tr> |
|||
<td>toggle-type</td> |
|||
<td>string</td> |
|||
<td> |
|||
If the item can be toggled, this property should be set to: |
|||
- "checkmark": Item is an independent togglable item |
|||
- "radio": Item is part of a group where only one item can be |
|||
toggled at a time |
|||
- "": Item cannot be toggled |
|||
</td> |
|||
<td>""</td> |
|||
</tr> |
|||
<tr> |
|||
<td>toggle-state</td> |
|||
<td>int</td> |
|||
<td> |
|||
Describe the current state of a "togglable" item. Can be one of: |
|||
- 0 = off |
|||
- 1 = on |
|||
- anything else = indeterminate |
|||
|
|||
Note: |
|||
The implementation does not itself handle ensuring that only one |
|||
item in a radio group is set to "on", or that a group does not have |
|||
"on" and "indeterminate" items simultaneously; maintaining this |
|||
policy is up to the toolkit wrappers. |
|||
</td> |
|||
<td>-1</td> |
|||
</tr> |
|||
<tr> |
|||
<td>children-display</td> |
|||
<td>string</td> |
|||
<td> |
|||
If the menu item has children this property should be set to |
|||
"submenu". |
|||
</td> |
|||
<td>""</td> |
|||
</tr> |
|||
<tr> |
|||
<td>disposition</td> |
|||
<td>string</td> |
|||
<td> |
|||
How the menuitem feels the information it's displaying to the |
|||
user should be presented. |
|||
- "normal" a standard menu item |
|||
- "informative" providing additional information to the user |
|||
- "warning" looking at potentially harmful results |
|||
- "alert" something bad could potentially happen |
|||
</td> |
|||
<td>"normal"</td> |
|||
</tr> |
|||
</table> |
|||
|
|||
Vendor specific properties can be added by prefixing them with |
|||
"x-<vendor>-". |
|||
]]></dox:d> |
|||
|
|||
<!-- Properties --> |
|||
<property name="Version" type="u" access="read"> |
|||
<dox:d> |
|||
Provides the version of the DBusmenu API that this API is |
|||
implementing. |
|||
</dox:d> |
|||
</property> |
|||
|
|||
<property name="TextDirection" type="s" access="read"> |
|||
<dox:d> |
|||
Represents the way the text direction of the application. This |
|||
allows the server to handle mismatches intelligently. For left- |
|||
to-right the string is "ltr" for right-to-left it is "rtl". |
|||
</dox:d> |
|||
</property> |
|||
|
|||
<property name="Status" type="s" access="read"> |
|||
<dox:d> |
|||
Tells if the menus are in a normal state or they believe that they |
|||
could use some attention. Cases for showing them would be if help |
|||
were referring to them or they accessors were being highlighted. |
|||
This property can have two values: "normal" in almost all cases and |
|||
"notice" when they should have a higher priority to be shown. |
|||
</dox:d> |
|||
</property> |
|||
|
|||
<property name="IconThemePath" type="as" access="read"> |
|||
<dox:d> |
|||
A list of directories that should be used for finding icons using |
|||
the icon naming spec. Idealy there should only be one for the icon |
|||
theme, but additional ones are often added by applications for |
|||
app specific icons. |
|||
</dox:d> |
|||
</property> |
|||
|
|||
<!-- Functions --> |
|||
|
|||
<method name="GetLayout"> |
|||
<dox:d> |
|||
Provides the layout and propertiers that are attached to the entries |
|||
that are in the layout. It only gives the items that are children |
|||
of the item that is specified in @a parentId. It will return all of the |
|||
properties or specific ones depending of the value in @a propertyNames. |
|||
|
|||
The format is recursive, where the second 'v' is in the same format |
|||
as the original 'a(ia{sv}av)'. Its content depends on the value |
|||
of @a recursionDepth. |
|||
</dox:d> |
|||
<arg type="i" name="parentId" direction="in"> |
|||
<dox:d>The ID of the parent node for the layout. For |
|||
grabbing the layout from the root node use zero.</dox:d> |
|||
</arg> |
|||
<arg type="i" name="recursionDepth" direction="in"> |
|||
<dox:d> |
|||
The amount of levels of recursion to use. This affects the |
|||
content of the second variant array. |
|||
- -1: deliver all the items under the @a parentId. |
|||
- 0: no recursion, the array will be empty. |
|||
- n: array will contains items up to 'n' level depth. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="as" name="propertyNames" direction="in" > |
|||
<dox:d> |
|||
The list of item properties we are |
|||
interested in. If there are no entries in the list all of |
|||
the properties will be sent. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="u" name="revision" direction="out"> |
|||
<dox:d>The revision number of the layout. For matching |
|||
with layoutUpdated signals.</dox:d> |
|||
</arg> |
|||
<arg type="(ia{sv}av)" name="layout" direction="out"> |
|||
<dox:d>The layout, as a recursive structure.</dox:d> |
|||
</arg> |
|||
</method> |
|||
|
|||
<method name="GetGroupProperties"> |
|||
<dox:d> |
|||
Returns the list of items which are children of @a parentId. |
|||
</dox:d> |
|||
<arg type="ai" name="ids" direction="in" > |
|||
<dox:d> |
|||
A list of ids that we should be finding the properties |
|||
on. If the list is empty, all menu items should be sent. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="as" name="propertyNames" direction="in" > |
|||
<dox:d> |
|||
The list of item properties we are |
|||
interested in. If there are no entries in the list all of |
|||
the properties will be sent. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="a(ia{sv})" name="properties" direction="out" > |
|||
<dox:d> |
|||
An array of property values. |
|||
An item in this area is represented as a struct following |
|||
this format: |
|||
@li id unsigned the item id |
|||
@li properties map(string => variant) the requested item properties |
|||
</dox:d> |
|||
</arg> |
|||
</method> |
|||
|
|||
<method name="GetProperty"> |
|||
<dox:d> |
|||
Get a signal property on a single item. This is not useful if you're |
|||
going to implement this interface, it should only be used if you're |
|||
debugging via a commandline tool. |
|||
</dox:d> |
|||
<arg type="i" name="id" direction="in"> |
|||
<dox:d>the id of the item which received the event</dox:d> |
|||
</arg> |
|||
<arg type="s" name="name" direction="in"> |
|||
<dox:d>the name of the property to get</dox:d> |
|||
</arg> |
|||
<arg type="v" name="value" direction="out"> |
|||
<dox:d>the value of the property</dox:d> |
|||
</arg> |
|||
</method> |
|||
|
|||
<method name="Event"> |
|||
<dox:d><![CDATA[ |
|||
This is called by the applet to notify the application an event happened on a |
|||
menu item. |
|||
|
|||
@a type can be one of the following: |
|||
|
|||
@li "clicked" |
|||
@li "hovered" |
|||
@li "opened" |
|||
@li "closed" |
|||
|
|||
Vendor specific events can be added by prefixing them with "x-<vendor>-" |
|||
]]></dox:d> |
|||
<arg type="i" name="id" direction="in" > |
|||
<dox:d>the id of the item which received the event</dox:d> |
|||
</arg> |
|||
<arg type="s" name="eventId" direction="in" > |
|||
<dox:d>the type of event</dox:d> |
|||
</arg> |
|||
<arg type="v" name="data" direction="in" > |
|||
<dox:d>event-specific data</dox:d> |
|||
</arg> |
|||
<arg type="u" name="timestamp" direction="in" > |
|||
<dox:d>The time that the event occured if available or the time the message was sent if not</dox:d> |
|||
</arg> |
|||
</method> |
|||
|
|||
<method name="EventGroup"> |
|||
<dox:d> |
|||
Used to pass a set of events as a single message for possibily several |
|||
different menuitems. This is done to optimize DBus traffic. |
|||
</dox:d> |
|||
<arg type="a(isvu)" name="events" direction="in"> |
|||
<dox:d> |
|||
An array of all the events that should be passed. This tuple should |
|||
match the parameters of the 'Event' signal. Which is roughly: |
|||
id, eventID, data and timestamp. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="ai" name="idErrors" direction="out"> |
|||
<dox:d> |
|||
I list of menuitem IDs that couldn't be found. If none of the ones |
|||
in the list can be found, a DBus error is returned. |
|||
</dox:d> |
|||
</arg> |
|||
</method> |
|||
|
|||
<method name="AboutToShow"> |
|||
<dox:d> |
|||
This is called by the applet to notify the application that it is about |
|||
to show the menu under the specified item. |
|||
</dox:d> |
|||
<arg type="i" name="id" direction="in"> |
|||
<dox:d> |
|||
Which menu item represents the parent of the item about to be shown. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="b" name="needUpdate" direction="out"> |
|||
<dox:d> |
|||
Whether this AboutToShow event should result in the menu being updated. |
|||
</dox:d> |
|||
</arg> |
|||
</method> |
|||
|
|||
<method name="AboutToShowGroup"> |
|||
<dox:d> |
|||
A function to tell several menus being shown that they are about to |
|||
be shown to the user. This is likely only useful for programitc purposes |
|||
so while the return values are returned, in general, the singular function |
|||
should be used in most user interacation scenarios. |
|||
</dox:d> |
|||
<arg type="ai" name="ids" direction="in"> |
|||
<dox:d> |
|||
The IDs of the menu items who's submenus are being shown. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="ai" name="updatesNeeded" direction="out"> |
|||
<dox:d> |
|||
The IDs of the menus that need updates. Note: if no update information |
|||
is needed the DBus message should set the no reply flag. |
|||
</dox:d> |
|||
</arg> |
|||
<arg type="ai" name="idErrors" direction="out"> |
|||
<dox:d> |
|||
I list of menuitem IDs that couldn't be found. If none of the ones |
|||
in the list can be found, a DBus error is returned. |
|||
</dox:d> |
|||
</arg> |
|||
</method> |
|||
|
|||
<!-- Signals --> |
|||
<signal name="ItemsPropertiesUpdated"> |
|||
<dox:d> |
|||
Triggered when there are lots of property updates across many items |
|||
so they all get grouped into a single dbus message. The format is |
|||
the ID of the item with a hashtable of names and values for those |
|||
properties. |
|||
</dox:d> |
|||
<arg type="a(ia{sv})" name="updatedProps" direction="out" /> |
|||
<arg type="a(ias)" name="removedProps" direction="out" /> |
|||
</signal> |
|||
<signal name="LayoutUpdated"> |
|||
<dox:d> |
|||
Triggered by the application to notify display of a layout update, up to |
|||
revision |
|||
</dox:d> |
|||
<arg type="u" name="revision" direction="out" > |
|||
<dox:d>The revision of the layout that we're currently on</dox:d> |
|||
</arg> |
|||
<arg type="i" name="parent" direction="out" > |
|||
<dox:d> |
|||
If the layout update is only of a subtree, this is the |
|||
parent item for the entries that have changed. It is zero if |
|||
the whole layout should be considered invalid. |
|||
</dox:d> |
|||
</arg> |
|||
</signal> |
|||
<signal name="ItemActivationRequested"> |
|||
<dox:d> |
|||
The server is requesting that all clients displaying this |
|||
menu open it to the user. This would be for things like |
|||
hotkeys that when the user presses them the menu should |
|||
open and display itself to the user. |
|||
</dox:d> |
|||
<arg type="i" name="id" direction="out" > |
|||
<dox:d>ID of the menu that should be activated</dox:d> |
|||
</arg> |
|||
<arg type="u" name="timestamp" direction="out" > |
|||
<dox:d>The time that the event occured</dox:d> |
|||
</arg> |
|||
</signal> |
|||
|
|||
<!-- End of interesting stuff --> |
|||
|
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,96 @@ |
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> |
|||
<node> |
|||
<interface name="org.kde.StatusNotifierItem"> |
|||
|
|||
<property name="Category" type="s" access="read"/> |
|||
<property name="Id" type="s" access="read"/> |
|||
<property name="Title" type="s" access="read"/> |
|||
<property name="Status" type="s" access="read"/> |
|||
<property name="WindowId" type="i" access="read"/> |
|||
|
|||
<!-- An additional path to add to the theme search path to find the icons specified above. --> |
|||
<property name="IconThemePath" type="s" access="read"/> |
|||
<property name="Menu" type="o" access="read"/> |
|||
<property name="ItemIsMenu" type="b" access="read"/> |
|||
|
|||
|
|||
<!-- main icon --> |
|||
<!-- names are preferred over pixmaps --> |
|||
<property name="IconName" type="s" access="read"/> |
|||
|
|||
<!--struct containing width, height and image data--> |
|||
<property name="IconPixmap" type="a(iiay)" access="read"> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/> |
|||
</property> |
|||
|
|||
<property name="OverlayIconName" type="s" access="read"/> |
|||
|
|||
<property name="OverlayIconPixmap" type="a(iiay)" access="read"> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/> |
|||
</property> |
|||
|
|||
|
|||
<!-- Requesting attention icon --> |
|||
<property name="AttentionIconName" type="s" access="read"/> |
|||
|
|||
<!--same definition as image--> |
|||
<property name="AttentionIconPixmap" type="a(iiay)" access="read"> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/> |
|||
</property> |
|||
|
|||
<property name="AttentionMovieName" type="s" access="read"/> |
|||
|
|||
|
|||
|
|||
<!-- tooltip data --> |
|||
|
|||
<!--(iiay) is an image--> |
|||
<property name="ToolTip" type="(sa(iiay)ss)" access="read"> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusToolTipStruct"/> |
|||
</property> |
|||
|
|||
|
|||
<!-- interaction: the systemtray wants the application to do something --> |
|||
<method name="ContextMenu"> |
|||
<!-- we're passing the coordinates of the icon, so the app knows where to put the popup window --> |
|||
<arg name="x" type="i" direction="in"/> |
|||
<arg name="y" type="i" direction="in"/> |
|||
</method> |
|||
|
|||
<method name="Activate"> |
|||
<arg name="x" type="i" direction="in"/> |
|||
<arg name="y" type="i" direction="in"/> |
|||
</method> |
|||
|
|||
<method name="SecondaryActivate"> |
|||
<arg name="x" type="i" direction="in"/> |
|||
<arg name="y" type="i" direction="in"/> |
|||
</method> |
|||
|
|||
<method name="Scroll"> |
|||
<arg name="delta" type="i" direction="in"/> |
|||
<arg name="orientation" type="s" direction="in"/> |
|||
</method> |
|||
|
|||
<!-- Signals: the client wants to change something in the status--> |
|||
<signal name="NewTitle"> |
|||
</signal> |
|||
|
|||
<signal name="NewIcon"> |
|||
</signal> |
|||
|
|||
<signal name="NewAttentionIcon"> |
|||
</signal> |
|||
|
|||
<signal name="NewOverlayIcon"> |
|||
</signal> |
|||
|
|||
<signal name="NewToolTip"> |
|||
</signal> |
|||
|
|||
<signal name="NewStatus"> |
|||
<arg name="status" type="s"/> |
|||
</signal> |
|||
|
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,42 @@ |
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> |
|||
<node> |
|||
<interface name="org.kde.StatusNotifierWatcher"> |
|||
|
|||
<!-- methods --> |
|||
<method name="RegisterStatusNotifierItem"> |
|||
<arg name="service" type="s" direction="in"/> |
|||
</method> |
|||
|
|||
<method name="RegisterStatusNotifierHost"> |
|||
<arg name="service" type="s" direction="in"/> |
|||
</method> |
|||
|
|||
|
|||
<!-- properties --> |
|||
|
|||
<property name="RegisteredStatusNotifierItems" type="as" access="read"> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringList"/> |
|||
</property> |
|||
|
|||
<property name="IsStatusNotifierHostRegistered" type="b" access="read"/> |
|||
|
|||
<property name="ProtocolVersion" type="i" access="read"/> |
|||
|
|||
|
|||
<!-- signals --> |
|||
|
|||
<signal name="StatusNotifierItemRegistered"> |
|||
<arg type="s"/> |
|||
</signal> |
|||
|
|||
<signal name="StatusNotifierItemUnregistered"> |
|||
<arg type="s"/> |
|||
</signal> |
|||
|
|||
<signal name="StatusNotifierHostRegistered"> |
|||
</signal> |
|||
|
|||
<signal name="StatusNotifierHostUnregistered"> |
|||
</signal> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,56 @@ |
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> |
|||
<node xmlns:dox="http://www.ayatana.org/dbus/dox.dtd"> |
|||
<dox:d><![CDATA[ |
|||
@mainpage |
|||
|
|||
An interface to register menus that are associated with a window in an application. The |
|||
main interface is documented here: @ref com::canonical::AppMenu::Registrar. |
|||
|
|||
The actual menus are transported using the dbusmenu protocol which is available |
|||
here: @ref com::canonical::dbusmenu. |
|||
]]></dox:d> |
|||
<interface name="com.canonical.AppMenu.Registrar" xmlns:dox="http://www.ayatana.org/dbus/dox.dtd"> |
|||
<dox:d> |
|||
An interface to register a menu from an application's window to be displayed in another |
|||
window. This manages that association between XWindow Window IDs and the dbus |
|||
address and object that provides the menu using the dbusmenu dbus interface. |
|||
</dox:d> |
|||
<method name="RegisterWindow"> |
|||
<dox:d><![CDATA[ |
|||
Associates a dbusmenu with a window |
|||
|
|||
/note this method assumes that the connection from the caller is the DBus connection |
|||
to use for the object. Applications that use multiple DBus connections will need to |
|||
ensure this method is called with the same connection that implmenets the object. |
|||
]]></dox:d> |
|||
<arg name="windowId" type="u" direction="in"> |
|||
<dox:d>The XWindow ID of the window</dox:d> |
|||
</arg> |
|||
<arg name="menuObjectPath" type="o" direction="in"> |
|||
<dox:d>The object on the dbus interface implementing the dbusmenu interface</dox:d> |
|||
</arg> |
|||
</method> |
|||
<method name="UnregisterWindow"> |
|||
<dox:d> |
|||
A method to allow removing a window from the database. Windows will also be removed |
|||
when the client drops off DBus so this is not required. It is polite though. And |
|||
important for testing. |
|||
</dox:d> |
|||
<arg name="windowId" type="u" direction="in"> |
|||
<dox:d>The XWindow ID of the window</dox:d> |
|||
</arg> |
|||
</method> |
|||
<method name="GetMenuForWindow"> |
|||
<dox:d>Gets the registered menu for a given window ID.</dox:d> |
|||
<arg name="windowId" type="u" direction="in"> |
|||
<dox:d>The XWindow ID of the window to get</dox:d> |
|||
</arg> |
|||
<arg name="service" type="s" direction="out"> |
|||
<dox:d>The address of the connection on DBus (e.g. :1.23 or org.example.service)</dox:d> |
|||
</arg> |
|||
<arg name="menuObjectPath" type="o" direction="out"> |
|||
<dox:d>The path to the object which implements the com.canonical.dbusmenu interface.</dox:d> |
|||
</arg> |
|||
</method> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,64 @@ |
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" |
|||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> |
|||
<node> |
|||
<interface name="org.fcitx.Fcitx.InputContext"> |
|||
<method name="FocusIn"> |
|||
</method> |
|||
<method name="FocusOut"> |
|||
</method> |
|||
<method name="Reset"> |
|||
</method> |
|||
<method name="SetCursorRect"> |
|||
<arg name="x" direction="in" type="i"/> |
|||
<arg name="y" direction="in" type="i"/> |
|||
<arg name="w" direction="in" type="i"/> |
|||
<arg name="h" direction="in" type="i"/> |
|||
</method> |
|||
<method name="SetCapacity"> |
|||
<arg name="caps" direction="in" type="u"/> |
|||
</method> |
|||
<method name="SetSurroundingText"> |
|||
<arg name="text" direction="in" type="s"/> |
|||
<arg name="cursor" direction="in" type="u"/> |
|||
<arg name="anchor" direction="in" type="u"/> |
|||
</method> |
|||
<method name="SetSurroundingTextPosition"> |
|||
<arg name="cursor" direction="in" type="u"/> |
|||
<arg name="anchor" direction="in" type="u"/> |
|||
</method> |
|||
<method name="DestroyIC"> |
|||
</method> |
|||
<method name="ProcessKeyEvent"> |
|||
<arg name="keyval" direction="in" type="u"/> |
|||
<arg name="keycode" direction="in" type="u"/> |
|||
<arg name="state" direction="in" type="u"/> |
|||
<arg name="type" direction="in" type="i"/> |
|||
<arg name="time" direction="in" type="u"/> |
|||
<arg name="ret" direction="out" type="i"/> |
|||
</method> |
|||
<signal name="CommitString"> |
|||
<arg name="str" type="s"/> |
|||
</signal> |
|||
<signal name="CurrentIM"> |
|||
<arg name="name" type="s"/> |
|||
<arg name="uniqueName" type="s"/> |
|||
<arg name="langCode" type="s"/> |
|||
</signal> |
|||
<signal name="UpdateFormattedPreedit"> |
|||
<arg name="str" type="a(si)" /> |
|||
<arg name="cursorpos" type="i"/> |
|||
<!-- qt4 / 5 seems use in/out differently --> |
|||
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="FcitxFormattedPreeditList" /> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="FcitxFormattedPreeditList" /> |
|||
</signal> |
|||
<signal name="ForwardKey"> |
|||
<arg name="keyval" type="u"/> |
|||
<arg name="state" type="u"/> |
|||
<arg name="type" type="i"/> |
|||
</signal> |
|||
<signal name="DeleteSurroundingText"> |
|||
<arg name="offset" type="i"/> |
|||
<arg name="nchar" type="u"/> |
|||
</signal> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,64 @@ |
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" |
|||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> |
|||
<node> |
|||
<interface name="org.fcitx.Fcitx.InputContext1"> |
|||
<method name="FocusIn"> |
|||
</method> |
|||
<method name="FocusOut"> |
|||
</method> |
|||
<method name="Reset"> |
|||
</method> |
|||
<method name="SetCursorRect"> |
|||
<arg name="x" direction="in" type="i"/> |
|||
<arg name="y" direction="in" type="i"/> |
|||
<arg name="w" direction="in" type="i"/> |
|||
<arg name="h" direction="in" type="i"/> |
|||
</method> |
|||
<method name="SetCapability"> |
|||
<arg name="caps" direction="in" type="t"/> |
|||
</method> |
|||
<method name="SetSurroundingText"> |
|||
<arg name="text" direction="in" type="s"/> |
|||
<arg name="cursor" direction="in" type="u"/> |
|||
<arg name="anchor" direction="in" type="u"/> |
|||
</method> |
|||
<method name="SetSurroundingTextPosition"> |
|||
<arg name="cursor" direction="in" type="u"/> |
|||
<arg name="anchor" direction="in" type="u"/> |
|||
</method> |
|||
<method name="DestroyIC"> |
|||
</method> |
|||
<method name="ProcessKeyEvent"> |
|||
<arg name="keyval" direction="in" type="u"/> |
|||
<arg name="keycode" direction="in" type="u"/> |
|||
<arg name="state" direction="in" type="u"/> |
|||
<arg name="type" direction="in" type="b"/> |
|||
<arg name="time" direction="in" type="u"/> |
|||
<arg name="ret" direction="out" type="b"/> |
|||
</method> |
|||
<signal name="CommitString"> |
|||
<arg name="str" type="s"/> |
|||
</signal> |
|||
<signal name="CurrentIM"> |
|||
<arg name="name" type="s"/> |
|||
<arg name="uniqueName" type="s"/> |
|||
<arg name="langCode" type="s"/> |
|||
</signal> |
|||
<signal name="UpdateFormattedPreedit"> |
|||
<arg name="str" type="a(si)" /> |
|||
<arg name="cursorpos" type="i"/> |
|||
<!-- qt4 / 5 seems use in/out differently --> |
|||
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="FcitxFormattedPreeditList" /> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="FcitxFormattedPreeditList" /> |
|||
</signal> |
|||
<signal name="ForwardKey"> |
|||
<arg name="keyval" type="u"/> |
|||
<arg name="state" type="u"/> |
|||
<arg name="type" type="b"/> |
|||
</signal> |
|||
<signal name="DeleteSurroundingText"> |
|||
<arg name="offset" type="i"/> |
|||
<arg name="nchar" type="u"/> |
|||
</signal> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,16 @@ |
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" |
|||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> |
|||
<node> |
|||
<interface name="org.fcitx.Fcitx.InputMethod"> |
|||
<method name="CreateICv3"> |
|||
<arg name="appname" direction="in" type="s"/> |
|||
<arg name="pid" direction="in" type="i"/> |
|||
<arg name="icid" direction="out" type="i"/> |
|||
<arg name="enable" direction="out" type="b"/> |
|||
<arg name="keyval1" direction="out" type="u"/> |
|||
<arg name="state1" direction="out" type="u"/> |
|||
<arg name="keyval2" direction="out" type="u"/> |
|||
<arg name="state2" direction="out" type="u"/> |
|||
</method> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,12 @@ |
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" |
|||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> |
|||
<node> |
|||
<interface name="org.fcitx.Fcitx.InputMethod1"> |
|||
<method name="CreateInputContext"> |
|||
<arg type="a(ss)" direction="in"/> |
|||
<arg type="o" direction="out"/> |
|||
<arg type="ay" direction="out"/> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="FcitxInputContextArgumentList" /> |
|||
</method> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,139 @@ |
|||
<?xml version="1.0"?> |
|||
<!-- |
|||
Copyright (C) 2017-2019 Red Hat, Inc. |
|||
|
|||
This library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2 of the License, or (at your option) any later version. |
|||
|
|||
This library is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with this library. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
Author: Alexander Larsson <alexl@redhat.com> |
|||
--> |
|||
|
|||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> |
|||
<!-- |
|||
org.freedesktop.IBus.Portal: |
|||
@short_description: Portal for ibus client access |
|||
|
|||
This interface is a minimal interface to IBus that is safe to expose to |
|||
clients. |
|||
--> |
|||
<interface name="org.freedesktop.IBus.Portal"> |
|||
<method name='CreateInputContext'> |
|||
<arg direction='in' type='s' name='client_name' /> |
|||
<arg direction='out' type='o' name='object_path' /> |
|||
</method> |
|||
</interface> |
|||
|
|||
<!-- This is a copy of the interface in inputcontext.c, they should be shared. |
|||
We want this for the code generator so that we can be sure we verify all |
|||
caller types, etc. |
|||
--> |
|||
<interface name='org.freedesktop.IBus.InputContext'> |
|||
<method name='ProcessKeyEvent'> |
|||
<arg direction='in' type='u' name='keyval' /> |
|||
<arg direction='in' type='u' name='keycode' /> |
|||
<arg direction='in' type='u' name='state' /> |
|||
<arg direction='out' type='b' name='handled' /> |
|||
</method> |
|||
<method name='SetCursorLocation'> |
|||
<arg direction='in' type='i' name='x' /> |
|||
<arg direction='in' type='i' name='y' /> |
|||
<arg direction='in' type='i' name='w' /> |
|||
<arg direction='in' type='i' name='h' /> |
|||
</method> |
|||
<method name='SetCursorLocationRelative'> |
|||
<arg direction='in' type='i' name='x' /> |
|||
<arg direction='in' type='i' name='y' /> |
|||
<arg direction='in' type='i' name='w' /> |
|||
<arg direction='in' type='i' name='h' /> |
|||
</method> |
|||
<method name='ProcessHandWritingEvent'> |
|||
<arg direction='in' type='ad' name='coordinates' /> |
|||
</method> |
|||
<method name='CancelHandWriting'> |
|||
<arg direction='in' type='u' name='n_strokes' /> |
|||
</method> |
|||
<method name='FocusIn' /> |
|||
<method name='FocusOut' /> |
|||
<method name='Reset' /> |
|||
<method name='SetCapabilities'> |
|||
<arg direction='in' type='u' name='caps' /> |
|||
</method> |
|||
<method name='PropertyActivate'> |
|||
<arg direction='in' type='s' name='name' /> |
|||
<arg direction='in' type='u' name='state' /> |
|||
</method> |
|||
<method name='SetEngine'> |
|||
<arg direction='in' type='s' name='name' /> |
|||
</method> |
|||
<method name='GetEngine'> |
|||
<arg direction='out' type='v' name='desc' /> |
|||
</method> |
|||
<method name='SetSurroundingText'> |
|||
<arg direction='in' type='v' name='text' /> |
|||
<arg direction='in' type='u' name='cursor_pos' /> |
|||
<arg direction='in' type='u' name='anchor_pos' /> |
|||
</method> |
|||
|
|||
<signal name='CommitText'> |
|||
<arg type='v' name='text' /> |
|||
</signal> |
|||
<signal name='ForwardKeyEvent'> |
|||
<arg type='u' name='keyval' /> |
|||
<arg type='u' name='keycode' /> |
|||
<arg type='u' name='state' /> |
|||
</signal> |
|||
<signal name='UpdatePreeditText'> |
|||
<arg type='v' name='text' /> |
|||
<arg type='u' name='cursor_pos' /> |
|||
<arg type='b' name='visible' /> |
|||
</signal> |
|||
<signal name='UpdatePreeditTextWithMode'> |
|||
<arg type='v' name='text' /> |
|||
<arg type='u' name='cursor_pos' /> |
|||
<arg type='b' name='visible' /> |
|||
<arg type='u' name='mode' /> |
|||
</signal> |
|||
<signal name='ShowPreeditText'/> |
|||
<signal name='HidePreeditText'/> |
|||
<signal name='UpdateAuxiliaryText'> |
|||
<arg type='v' name='text' /> |
|||
<arg type='b' name='visible' /> |
|||
</signal> |
|||
<signal name='ShowAuxiliaryText'/> |
|||
<signal name='HideAuxiliaryText'/> |
|||
<signal name='UpdateLookupTable'> |
|||
<arg type='v' name='table' /> |
|||
<arg type='b' name='visible' /> |
|||
</signal> |
|||
<signal name='ShowLookupTable'/> |
|||
<signal name='HideLookupTable'/> |
|||
<signal name='PageUpLookupTable'/> |
|||
<signal name='PageDownLookupTable'/> |
|||
<signal name='CursorUpLookupTable'/> |
|||
<signal name='CursorDownLookupTable'/> |
|||
<signal name='RegisterProperties'> |
|||
<arg type='v' name='props' /> |
|||
</signal> |
|||
<signal name='UpdateProperty'> |
|||
<arg type='v' name='prop' /> |
|||
</signal> |
|||
|
|||
<property name='ContentType' type='(uu)' access='write' /> |
|||
<property name='ClientCommitPreedit' type='(b)' access='write' /> |
|||
</interface> |
|||
|
|||
<interface name='org.freedesktop.IBus.Service'> |
|||
<method name='Destroy' /> |
|||
</interface> |
|||
|
|||
</node> |
|||
@ -0,0 +1,377 @@ |
|||
<?xml version="1.0"?> |
|||
<!-- |
|||
Copyright (C) 2015 Red Hat, Inc. |
|||
|
|||
This library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2 of the License, or (at your option) any later version. |
|||
|
|||
This library is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with this library. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
Author: Alexander Larsson <alexl@redhat.com> |
|||
--> |
|||
|
|||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> |
|||
<!-- |
|||
org.freedesktop.portal.FileChooser: |
|||
@short_description: File chooser portal |
|||
|
|||
The FileChooser portal allows sandboxed applications to ask |
|||
the user for access to files outside the sandbox. The portal |
|||
backend will present the user with a file chooser dialog. |
|||
|
|||
The selected files will be made accessible to the application |
|||
via the document portal, and the returned URI will point |
|||
into the document portal fuse filesystem in /run/user/$UID/doc/. |
|||
|
|||
This documentation describes version 3 of this interface. |
|||
--> |
|||
<interface name="org.freedesktop.portal.FileChooser"> |
|||
<!-- |
|||
OpenFile: |
|||
@parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link> |
|||
@title: Title for the file chooser dialog |
|||
@options: Vardict with optional further information |
|||
@handle: Object path for the #org.freedesktop.portal.Request object representing this call |
|||
|
|||
Asks to open one or more files. |
|||
|
|||
Supported keys in the @options vardict include: |
|||
<variablelist> |
|||
<varlistentry> |
|||
<term>handle_token s</term> |
|||
<listitem><para> |
|||
A string that will be used as the last element of the @handle. Must be a valid |
|||
object path element. See the #org.freedesktop.portal.Request documentation for |
|||
more information about the @handle. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>accept_label s</term> |
|||
<listitem><para> |
|||
Label for the accept button. Mnemonic underlines are allowed. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>modal b</term> |
|||
<listitem><para> |
|||
Whether the dialog should be modal. Default is yes. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>multiple b</term> |
|||
<listitem><para> |
|||
Whether multiple files can be selected or not. Default is single-selection. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>directory b</term> |
|||
<listitem><para> |
|||
Whether to select for folders instead of files. Default is to select files. This option was added in version 3. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>filters a(sa(us))</term> |
|||
<listitem> |
|||
<para> |
|||
List of serialized file filters. |
|||
</para> |
|||
<para> |
|||
Each item in the array specifies a single filter to offer to the user. |
|||
The first string is a user-visible name for the filter. The a(us) |
|||
specifies a list of filter strings, which can be either a glob-style pattern |
|||
(indicated by 0) or a mimetype (indicated by 1). Patterns are case-sensitive. |
|||
To match different capitalizations of, e.g. '*.ico', use a pattern like |
|||
'*.[iI][cC][oO]'. |
|||
</para> |
|||
<para> |
|||
Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] |
|||
</para> |
|||
<para> |
|||
Note that filters are purely there to aid the user in making a useful selection. |
|||
The portal may still allow the user to select files that don't match any filter |
|||
criteria, and applications must be prepared to handle that. |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_filter (sa(us))</term> |
|||
<listitem><para> |
|||
Request that this filter be set by default at dialog creation. If |
|||
the filters list is nonempty, it should match a filter in the |
|||
list to set the default filter from the list. Alternatively, it |
|||
may be specified when the list is empty to apply the filter |
|||
unconditionally. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>choices a(ssa(ss)s)</term> |
|||
<listitem> |
|||
<para> |
|||
List of serialized combo boxes to add to the file chooser. |
|||
</para> |
|||
<para> |
|||
For each element, the first string is an ID that will be returned |
|||
with the response, the second string is a user-visible label. The |
|||
a(ss) is the list of choices, each being an ID and a |
|||
user-visible label. The final string is the initial selection, |
|||
or "", to let the portal decide which choice will be initially selected. |
|||
None of the strings, except for the initial selection, should be empty. |
|||
</para> |
|||
<para> |
|||
As a special case, passing an empty array for the list of choices |
|||
indicates a boolean choice that is typically displayed as a check |
|||
button, using "true" and "false" as the choices. |
|||
</para> |
|||
<para> |
|||
Example: [('encoding', 'Encoding', [('utf8', 'Unicode (UTF-8)'), ('latin15', 'Western')], 'latin15'), ('reencode', 'Reencode', [], 'false')] |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
</variablelist> |
|||
|
|||
The following results get returned via the #org.freedesktop.portal.Request::Response signal: |
|||
<variablelist> |
|||
<varlistentry> |
|||
<term>uris as</term> |
|||
<listitem><para> |
|||
An array of strings containing the uris of the selected files. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>choices a(ss)</term> |
|||
<listitem> |
|||
<para> |
|||
An array of pairs of strings, the first string being the ID of a |
|||
combobox that was passed into this call, the second string being |
|||
the selected option. |
|||
</para> |
|||
<para> |
|||
Example: [('encoding', 'utf8'), ('reencode', 'true')] |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_filter (sa(us))</term> |
|||
<listitem> |
|||
<para> |
|||
The filter that was selected. This may match a filter in the |
|||
filter list or another filter that was applied unconditionally. |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
</variablelist> |
|||
--> |
|||
<method name="OpenFile"> |
|||
<arg type="s" name="parent_window" direction="in"/> |
|||
<arg type="s" name="title" direction="in"/> |
|||
<arg type="a{sv}" name="options" direction="in"/> |
|||
<arg type="o" name="handle" direction="out"/> |
|||
</method> |
|||
<!-- |
|||
SaveFile: |
|||
@parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link> |
|||
@title: Title for the file chooser dialog |
|||
@options: Vardict with optional further information |
|||
@handle: Object path for the #org.freedesktop.portal.Request object representing this call |
|||
|
|||
Asks for a location to save a file. |
|||
|
|||
Supported keys in the @options vardict include: |
|||
<variablelist> |
|||
<varlistentry> |
|||
<term>handle_token s</term> |
|||
<listitem><para> |
|||
A string that will be used as the last element of the @handle. Must be a valid |
|||
object path element. See the #org.freedesktop.portal.Request documentation for |
|||
more information about the @handle. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>accept_label s</term> |
|||
<listitem><para> |
|||
Label for the accept button. Mnemonic underlines are allowed. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>modal b</term> |
|||
<listitem><para> |
|||
Whether the dialog should be modal. Default is yes. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>filters a(sa(us))</term> |
|||
<listitem><para> |
|||
List of serialized file filters. |
|||
See org.freedesktop.portal.FileChooser.OpenFile() for details. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_filter (sa(us))</term> |
|||
<listitem><para> |
|||
Request that this filter be set by default at dialog creation. |
|||
See org.freedesktop.portal.FileChooser.OpenFile() for details. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>choices a(ssa(ss)s)</term> |
|||
<listitem><para> |
|||
List of serialized combo boxes. |
|||
See org.freedesktop.portal.FileChooser.OpenFile() for details. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_name s</term> |
|||
<listitem><para>Suggested filename.</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_folder ay</term> |
|||
<listitem> |
|||
<para> |
|||
Suggested folder to save the file in. The byte array is expected to be null-terminated. |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_file ay</term> |
|||
<listitem> |
|||
<para> |
|||
The current file (when saving an existing file). The byte array is expected to be null-terminated. |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
</variablelist> |
|||
|
|||
The following results get returned via the #org.freedesktop.portal.Request::Response signal: |
|||
<variablelist> |
|||
<varlistentry> |
|||
<term>uris as</term> |
|||
<listitem><para> |
|||
An array of strings containing the uri of the selected file. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>choices a(ss)</term> |
|||
<listitem><para> |
|||
An array of pairs of strings, corresponding to the passed-in choices. |
|||
See org.freedesktop.portal.FileChooser.OpenFile() for details. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_filter (sa(us))</term> |
|||
<listitem> |
|||
<para> |
|||
The filter that was selected. |
|||
See org.freedesktop.portal.FileChooser.OpenFile() for details. |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
</variablelist> |
|||
--> |
|||
<method name="SaveFile"> |
|||
<arg type="s" name="parent_window" direction="in"/> |
|||
<arg type="s" name="title" direction="in"/> |
|||
<arg type="a{sv}" name="options" direction="in"/> |
|||
<arg type="o" name="handle" direction="out"/> |
|||
</method> |
|||
<!-- |
|||
SaveFiles: |
|||
@parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link> |
|||
@title: Title for the file chooser dialog |
|||
@options: Vardict with optional further information |
|||
@handle: Object path for the #org.freedesktop.portal.Request object representing this call |
|||
|
|||
Asks for a folder as a location to save one or more files. The |
|||
names of the files will be used as-is and appended to the |
|||
selected folder's path in the list of returned files. If the |
|||
selected folder already contains a file with one of the given |
|||
names, the portal may prompt or take some other action to |
|||
construct a unique file name and return that instead. |
|||
|
|||
Supported keys in the @options vardict include: |
|||
<variablelist> |
|||
<varlistentry> |
|||
<term>handle_token s</term> |
|||
<listitem><para> |
|||
A string that will be used as the last element of the |
|||
@handle. Must be a valid object path element. See the |
|||
#org.freedesktop.portal.Request documentation for more |
|||
information about the @handle. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>accept_label s</term> |
|||
<listitem><para> |
|||
Label for the accept button. Mnemonic underlines are allowed. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>modal b</term> |
|||
<listitem><para> |
|||
Whether the dialog should be modal. Default is yes. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>choices a(ssa(ss)s)</term> |
|||
<listitem><para> |
|||
List of serialized combo boxes. |
|||
See org.freedesktop.portal.FileChooser.OpenFile() for details. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>current_folder ay</term> |
|||
<listitem> |
|||
<para> |
|||
Suggested folder to save the files in. The byte array is |
|||
expected to be null-terminated. |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>files aay</term> |
|||
<listitem> |
|||
<para> |
|||
An array of file names to be saved. The byte arrays are |
|||
expected to be null-terminated. |
|||
</para> |
|||
</listitem> |
|||
</varlistentry> |
|||
</variablelist> |
|||
|
|||
The following results get returned via the |
|||
#org.freedesktop.portal.Request::Response signal: |
|||
<variablelist> |
|||
<varlistentry> |
|||
<term>uris as</term> |
|||
<listitem><para> |
|||
An array of strings containing the uri corresponding to |
|||
each file given by @options, in the same order. Note that |
|||
the file names may have changed, for example if a file |
|||
with the same name in the selected folder already exists. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
<varlistentry> |
|||
<term>choices a(ss)</term> |
|||
<listitem><para> |
|||
An array of pairs of strings, corresponding to the passed-in choices. |
|||
See org.freedesktop.portal.FileChooser.OpenFile() for details. |
|||
</para></listitem> |
|||
</varlistentry> |
|||
</variablelist> |
|||
--> |
|||
<method name="SaveFiles"> |
|||
<arg type="s" name="parent_window" direction="in"/> |
|||
<arg type="s" name="title" direction="in"/> |
|||
<arg type="a{sv}" name="options" direction="in"/> |
|||
<arg type="o" name="handle" direction="out"/> |
|||
</method> |
|||
<property name="version" type="u" access="read"/> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,86 @@ |
|||
<?xml version="1.0"?> |
|||
<!-- |
|||
Copyright (C) 2015 Red Hat, Inc. |
|||
|
|||
This library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2 of the License, or (at your option) any later version. |
|||
|
|||
This library is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with this library. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
Author: Alexander Larsson <alexl@redhat.com> |
|||
--> |
|||
|
|||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> |
|||
<!-- |
|||
org.freedesktop.portal.Request: |
|||
@short_description: Shared request interface |
|||
|
|||
The Request interface is shared by all portal interfaces. When a |
|||
portal method is called, the reply includes a handle (i.e. object path) |
|||
for a Request object, which will stay alive for the duration of the |
|||
user interaction related to the method call. |
|||
|
|||
The portal indicates that a portal request interaction is over by |
|||
emitting the #org.freedesktop.portal.Request::Response signal on the |
|||
Request object. |
|||
|
|||
The application can abort the interaction calling |
|||
org.freedesktop.portal.Request.Close() on the Request object. |
|||
|
|||
Since version 0.9 of xdg-desktop-portal, the handle will be of the form |
|||
/org/freedesktop/portal/desktop/request/SENDER/TOKEN, |
|||
where SENDER is the callers unique name, with the initial ':' removed and |
|||
all '.' replaced by '_', and TOKEN is a unique token that the caller provided |
|||
with the handle_token key in the options vardict. |
|||
|
|||
This change was made to let applications subscribe to the Response signal before |
|||
making the initial portal call, thereby avoiding a race condition. It is recommended |
|||
that the caller should verify that the returned handle is what it expected, and update |
|||
its signal subscription if it isn't. This ensures that applications will work with both |
|||
old and new versions of xdg-desktop-portal. |
|||
|
|||
The token that the caller provides should be unique and not guessable. To avoid clashes |
|||
with calls made from unrelated libraries, it is a good idea to use a per-library prefix |
|||
combined with a random number. |
|||
--> |
|||
<interface name="org.freedesktop.portal.Request"> |
|||
|
|||
<!-- |
|||
Close: |
|||
|
|||
Closes the portal request to which this object refers and ends all |
|||
related user interaction (dialogs, etc). |
|||
A Response signal will not be emitted in this case. |
|||
--> |
|||
<method name="Close"> |
|||
</method> |
|||
|
|||
<!-- |
|||
Response: |
|||
@response: Numeric response |
|||
@results: Vardict with results. The keys and values in the vardict depend on the request. |
|||
|
|||
Emitted when the user interaction for a portal request is over. |
|||
|
|||
The @response indicates how the user interaction ended: |
|||
<simplelist> |
|||
<member>0: Success, the request is carried out</member> |
|||
<member>1: The user cancelled the interaction</member> |
|||
<member>2: The user interaction was ended in some other way</member> |
|||
</simplelist> |
|||
--> |
|||
<signal name="Response"> |
|||
<arg type="u" name="response"/> |
|||
<arg type="a{sv}" name="results"/> |
|||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/> |
|||
</signal> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,99 @@ |
|||
<?xml version="1.0"?> |
|||
<!-- |
|||
Copyright (C) 2018 Igalia S.L. |
|||
|
|||
This library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2 of the License, or (at your option) any later version. |
|||
|
|||
This library is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with this library. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
Author: Patrick Griffis <pgriffis@igalia.com> |
|||
--> |
|||
|
|||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> |
|||
<!-- |
|||
org.freedesktop.portal.Settings: |
|||
@short_description: Settings interface |
|||
|
|||
This interface provides read-only access to a small number |
|||
of host settings required for toolkits similar to XSettings. |
|||
It is not for general purpose settings. |
|||
|
|||
Currently the interface provides the following keys: |
|||
|
|||
<variablelist> |
|||
<varlistentry> |
|||
<term>org.freedesktop.appearance color-scheme u</term> |
|||
<listitem><para> |
|||
Indicates the system's preferred color scheme. |
|||
Supported values are: |
|||
<simplelist> |
|||
<member>0: No preference</member> |
|||
<member>1: Prefer dark appearance</member> |
|||
<member>2: Prefer light appearance</member> |
|||
</simplelist> |
|||
Unknown values should be treated as 0 (no preference). |
|||
</para></listitem> |
|||
</varlistentry> |
|||
</variablelist> |
|||
|
|||
Implementations can provide other keys; they are entirely |
|||
implementation details that are undocumented. If you are a |
|||
toolkit and want to use this please open an issue. |
|||
|
|||
This documentation describes version 1 of this interface. |
|||
--> |
|||
<interface name="org.freedesktop.portal.Settings"> |
|||
|
|||
<!-- |
|||
ReadAll: |
|||
@namespaces: List of namespaces to filter results by, supports simple globbing explained below. |
|||
@value: Dictionary of namespaces to its keys and values. |
|||
|
|||
If @namespaces is an empty array or contains an empty string it matches all. Globbing is supported but only for |
|||
trailing sections, e.g. "org.example.*". |
|||
--> |
|||
<method name='ReadAll'> |
|||
<arg name='namespaces' type='as'/> |
|||
<arg name='value' direction='out' type='a{sa{sv}}'/> |
|||
</method> |
|||
|
|||
<!-- |
|||
Read: |
|||
@namespace: Namespace to look up @key in. |
|||
@key: The key to get. |
|||
@value: The value @key is set to. |
|||
|
|||
Reads a single value. Returns an error on any unknown namespace or key. |
|||
--> |
|||
<method name='Read'> |
|||
<arg name='namespace' type='s'/> |
|||
<arg name='key' type='s'/> |
|||
<arg name='value' direction='out' type='v'/> |
|||
</method> |
|||
|
|||
<!-- |
|||
SettingChanged: |
|||
@namespace: Namespace of changed setting. |
|||
@key: The key of changed setting. |
|||
@value: The new value. |
|||
|
|||
Emitted when a setting changes. |
|||
--> |
|||
<signal name='SettingChanged'> |
|||
<arg name='namespace' direction='out' type='s'/> |
|||
<arg name='key' direction='out' type='s'/> |
|||
<arg name='value' direction='out' type='v'/> |
|||
</signal> |
|||
|
|||
<property name="version" type="u" access="read"/> |
|||
</interface> |
|||
</node> |
|||
@ -0,0 +1,17 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4"> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|||
</PackageReference> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,795 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
using Microsoft.CodeAnalysis.Diagnostics; |
|||
using Microsoft.CodeAnalysis.Operations; |
|||
|
|||
namespace Avalonia.Analyzers; |
|||
|
|||
public partial class AvaloniaPropertyAnalyzer |
|||
{ |
|||
public class CompileAnalyzer |
|||
{ |
|||
/// <summary>
|
|||
/// A dictionary that maps field/property symbols to the AvaloniaProperty objects assigned to them.
|
|||
/// </summary>
|
|||
private ImmutableDictionary<ISymbol, AvaloniaPropertyDescription> _avaloniaPropertyDescriptions = null!; |
|||
|
|||
/// <summary>
|
|||
/// Maps properties onto all AvaloniaProperty objects that they may be intended to represent.
|
|||
/// </summary>
|
|||
private ImmutableDictionary<IPropertySymbol, ImmutableArray<AvaloniaPropertyDescription>> _clrPropertyToAvaloniaProperties = null!; |
|||
|
|||
private readonly INamedTypeSymbol _stringType; |
|||
private readonly INamedTypeSymbol _avaloniaObjectType; |
|||
private readonly ImmutableHashSet<IMethodSymbol> _getValueMethods; |
|||
private readonly ImmutableHashSet<IMethodSymbol> _setValueMethods; |
|||
private readonly ImmutableHashSet<IMethodSymbol> _allGetSetMethods; |
|||
private readonly INamedTypeSymbol _avaloniaPropertyType; |
|||
private readonly INamedTypeSymbol _styledPropertyType; |
|||
private readonly INamedTypeSymbol _attachedPropertyType; |
|||
private readonly INamedTypeSymbol _directPropertyType; |
|||
private readonly INamedTypeSymbol? _userControlType; |
|||
private readonly INamedTypeSymbol? _topLevelType; |
|||
private readonly ImmutableHashSet<INamedTypeSymbol> _allAvaloniaPropertyTypes; |
|||
private readonly ImmutableDictionary<INamedTypeSymbol, ITypeParameterSymbol> _propertyValueTypeParams; |
|||
private readonly ImmutableHashSet<IMethodSymbol> _avaloniaPropertyRegisterMethods; |
|||
private readonly ImmutableHashSet<IMethodSymbol> _avaloniaPropertyAddOwnerMethods; |
|||
private readonly ImmutableHashSet<IMethodSymbol> _allAvaloniaPropertyMethods; |
|||
private readonly ImmutableDictionary<IMethodSymbol, ITypeParameterSymbol> _ownerTypeParams; |
|||
private readonly ImmutableDictionary<IMethodSymbol, ITypeParameterSymbol> _valueTypeParams; |
|||
private readonly ImmutableDictionary<IMethodSymbol, ITypeParameterSymbol> _hostTypeParams; |
|||
private readonly ImmutableDictionary<IMethodSymbol, IParameterSymbol> _inheritsParams; |
|||
private readonly ImmutableDictionary<IMethodSymbol, IParameterSymbol> _ownerParams; |
|||
|
|||
public CompileAnalyzer(CompilationStartAnalysisContext context, INamedTypeSymbol avaloniaObjectType) |
|||
{ |
|||
var methodComparer = SymbolEqualityComparer<IMethodSymbol>.Default; |
|||
|
|||
_stringType = GetTypeOrThrow("System.String"); |
|||
_avaloniaObjectType = avaloniaObjectType; |
|||
_getValueMethods = _avaloniaObjectType.GetMembers("GetValue").OfType<IMethodSymbol>().ToImmutableHashSet(methodComparer); |
|||
_setValueMethods = _avaloniaObjectType.GetMembers("SetValue").OfType<IMethodSymbol>().ToImmutableHashSet(methodComparer); |
|||
_allGetSetMethods = _getValueMethods.Concat(_setValueMethods).ToImmutableHashSet(methodComparer); |
|||
|
|||
_avaloniaPropertyType = GetTypeOrThrow("Avalonia.AvaloniaProperty"); |
|||
_styledPropertyType = GetTypeOrThrow("Avalonia.StyledProperty`1"); |
|||
_attachedPropertyType = GetTypeOrThrow("Avalonia.AttachedProperty`1"); |
|||
_directPropertyType = GetTypeOrThrow("Avalonia.DirectProperty`2"); |
|||
|
|||
_userControlType = context.Compilation.GetTypeByMetadataName("Avalonia.Controls.UserControl"); |
|||
_topLevelType = context.Compilation.GetTypeByMetadataName("Avalonia.Controls.TopLevel"); |
|||
|
|||
_avaloniaPropertyRegisterMethods = _avaloniaPropertyType.GetMembers() |
|||
.OfType<IMethodSymbol>().Where(m => m.Name.StartsWith("Register")).ToImmutableHashSet(methodComparer); |
|||
|
|||
_allAvaloniaPropertyTypes = new[] { _styledPropertyType, _attachedPropertyType, _directPropertyType }.ToImmutableHashSet(SymbolEqualityComparer<INamedTypeSymbol>.Default); |
|||
|
|||
_propertyValueTypeParams = _allAvaloniaPropertyTypes.Select(p => p.TypeParameters.First(t => t.Name == "TValue")) |
|||
.Where(p => p != null).Cast<ITypeParameterSymbol>() |
|||
.ToImmutableDictionary(p => p.ContainingType, SymbolEqualityComparer<INamedTypeSymbol>.Default); |
|||
|
|||
_avaloniaPropertyAddOwnerMethods = _allAvaloniaPropertyTypes |
|||
.SelectMany(t => t.GetMembers("AddOwner").OfType<IMethodSymbol>()).ToImmutableHashSet(methodComparer); |
|||
|
|||
_allAvaloniaPropertyMethods = _avaloniaPropertyRegisterMethods.Concat(_avaloniaPropertyAddOwnerMethods).ToImmutableHashSet(methodComparer); |
|||
|
|||
_ownerTypeParams = GetParamDictionary("TOwner", m => m.TypeParameters); |
|||
_valueTypeParams = GetParamDictionary("TValue", m => m.TypeParameters); |
|||
_hostTypeParams = GetParamDictionary("THost", m => m.TypeParameters); |
|||
_inheritsParams = GetParamDictionary("inherits", m => m.Parameters); |
|||
_ownerParams = GetParamDictionary("ownerType", m => m.Parameters); |
|||
|
|||
RegisterAvaloniaPropertySymbols(context.Compilation, context.CancellationToken); |
|||
|
|||
context.RegisterOperationAction(AnalyzeFieldInitializer, OperationKind.FieldInitializer); |
|||
context.RegisterOperationAction(AnalyzePropertyInitializer, OperationKind.PropertyInitializer); |
|||
context.RegisterOperationAction(AnalyzePropertyStorageAssignment, OperationKind.SimpleAssignment); |
|||
context.RegisterOperationAction(AnalyzePropertyWrapperAssignment, OperationKind.SimpleAssignment); |
|||
context.RegisterOperationAction(AnalyzeMethodInvocation, OperationKind.Invocation); |
|||
|
|||
context.RegisterSymbolAction(AnalyzeWrapperCrlProperty, SymbolKind.Property); |
|||
|
|||
if (context.Compilation.Language == LanguageNames.CSharp) |
|||
{ |
|||
context.RegisterCodeBlockAction(AnalyzePropertyMethods); |
|||
} |
|||
|
|||
INamedTypeSymbol GetTypeOrThrow(string name) => context.Compilation.GetTypeByMetadataName(name) ?? throw new KeyNotFoundException($"Could not locate {name} in the compilation context."); |
|||
|
|||
ImmutableDictionary<IMethodSymbol, TSymbol> GetParamDictionary<TSymbol>(string name, Func<IMethodSymbol, IEnumerable<TSymbol>> methodSymbolSelector) where TSymbol : ISymbol => _allAvaloniaPropertyMethods |
|||
.Select(m => methodSymbolSelector(m).SingleOrDefault(p => p.Name == name)) |
|||
.Where(p => p != null).Cast<TSymbol>() |
|||
.ToImmutableDictionary(p => (IMethodSymbol)p.ContainingSymbol, SymbolEqualityComparer<IMethodSymbol>.Default); |
|||
} |
|||
|
|||
private bool IsAvaloniaPropertyStorage(IFieldSymbol symbol) => symbol.Type is INamedTypeSymbol namedType && IsAvaloniaPropertyType(namedType, _allAvaloniaPropertyTypes); |
|||
private bool IsAvaloniaPropertyStorage(IPropertySymbol symbol) => symbol.Type is INamedTypeSymbol namedType && IsAvaloniaPropertyType(namedType, _allAvaloniaPropertyTypes); |
|||
|
|||
private void RegisterAvaloniaPropertySymbols(Compilation compilation, CancellationToken cancellationToken) |
|||
{ |
|||
var namespaceStack = new Stack<INamespaceSymbol>(); |
|||
namespaceStack.Push(compilation.GlobalNamespace); |
|||
|
|||
var types = new List<INamedTypeSymbol>(); |
|||
|
|||
while (namespaceStack.Count > 0) |
|||
{ |
|||
var current = namespaceStack.Pop(); |
|||
|
|||
types.AddRange(current.GetTypeMembers()); |
|||
|
|||
foreach (var child in current.GetNamespaceMembers()) |
|||
{ |
|||
namespaceStack.Push(child); |
|||
} |
|||
} |
|||
|
|||
var avaloniaPropertyStorageSymbols = new ConcurrentBag<ISymbol>(); |
|||
|
|||
var propertyDescriptions = new ConcurrentDictionary<ISymbol, AvaloniaPropertyDescription>(SymbolEqualityComparer.Default); |
|||
|
|||
// key initializes value
|
|||
var fieldInitializations = new ConcurrentDictionary<ISymbol, ISymbol>(SymbolEqualityComparer.Default); |
|||
|
|||
var parallelOptions = new ParallelOptions() { CancellationToken = cancellationToken }; |
|||
|
|||
var semanticModels = new ConcurrentDictionary<SyntaxTree, SemanticModel>(); |
|||
|
|||
Parallel.ForEach(types, parallelOptions, type => |
|||
{ |
|||
try |
|||
{ |
|||
foreach (var member in type.GetMembers()) |
|||
{ |
|||
switch (member) |
|||
{ |
|||
case IFieldSymbol fieldSymbol when IsAvaloniaPropertyStorage(fieldSymbol): |
|||
avaloniaPropertyStorageSymbols.Add(fieldSymbol); |
|||
break; |
|||
case IPropertySymbol propertySymbol when IsAvaloniaPropertyStorage(propertySymbol): |
|||
avaloniaPropertyStorageSymbols.Add(propertySymbol); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
foreach (var constructor in type.StaticConstructors) |
|||
{ |
|||
foreach (var syntaxRef in constructor.DeclaringSyntaxReferences.Where(sr => compilation.ContainsSyntaxTree(sr.SyntaxTree))) |
|||
{ |
|||
var (node, model) = GetNodeAndModel(syntaxRef); |
|||
|
|||
foreach (var descendant in node.DescendantNodes().Where(n => n.IsKind(SyntaxKind.SimpleAssignmentExpression))) |
|||
{ |
|||
var assignmentOperation = (IAssignmentOperation)model.GetOperation(descendant, cancellationToken)!; |
|||
|
|||
if (GetReferencedFieldOrProperty(assignmentOperation.Target) is { } target) |
|||
{ |
|||
RegisterAssignment(target, assignmentOperation.Value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) when (ex is not OperationCanceledException) |
|||
{ |
|||
throw new AvaloniaAnalysisException($"Failed to find AvaloniaProperty objects in {type}.", ex); |
|||
} |
|||
}); |
|||
|
|||
Parallel.ForEach(avaloniaPropertyStorageSymbols, parallelOptions, symbol => |
|||
{ |
|||
foreach (var syntaxRef in symbol.DeclaringSyntaxReferences.Where(sr => compilation.ContainsSyntaxTree(sr.SyntaxTree))) |
|||
{ |
|||
var (node, model) = GetNodeAndModel(syntaxRef); |
|||
|
|||
var operation = node.ChildNodes().Select(n => model.GetOperation(n, cancellationToken)).OfType<ISymbolInitializerOperation>().FirstOrDefault(); |
|||
|
|||
if (operation == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
RegisterAssignment(symbol, operation.Value); |
|||
} |
|||
}); |
|||
|
|||
// we have recorded every Register and AddOwner call. Now follow assignment chains.
|
|||
Parallel.ForEach(fieldInitializations.Keys.Intersect(propertyDescriptions.Keys, SymbolEqualityComparer.Default).ToArray(), root => |
|||
{ |
|||
var propertyDescription = propertyDescriptions[root]; |
|||
var owner = propertyDescription.AssignedTo[root]; |
|||
|
|||
var current = root; |
|||
do |
|||
{ |
|||
var target = fieldInitializations[current]; |
|||
|
|||
propertyDescription.SetAssignment(target, new(owner.Type, target.Locations[0])); // This loop handles simple assignment operations, so do NOT change the owner type
|
|||
propertyDescriptions[target] = propertyDescription; |
|||
|
|||
fieldInitializations.TryGetValue(target, out current); |
|||
} |
|||
while (current != null); |
|||
}); |
|||
|
|||
var clrPropertyWrapCandidates = new ConcurrentBag<(IPropertySymbol, AvaloniaPropertyDescription)>(); |
|||
|
|||
var propertyDescriptionsByName = propertyDescriptions.Values.ToLookup(p => p.Name, p => (property: p, owners: p.OwnerTypes.Select(t => t.Type).ToImmutableHashSet(SymbolEqualityComparer.Default))); |
|||
|
|||
// Detect CLR properties that provide syntatic wrapping around an AvaloniaProperty (or potentially multiple, which leads to a warning diagnostic)
|
|||
Parallel.ForEach(propertyDescriptions.Values, propertyDescription => |
|||
{ |
|||
var nameMatches = propertyDescriptionsByName[propertyDescription.Name]; |
|||
|
|||
foreach (var ownerType in propertyDescription.OwnerTypes.Select(o => o.Type).Distinct(SymbolEqualityComparer<ITypeSymbol>.Default)) |
|||
{ |
|||
if (ownerType.GetMembers(propertyDescription.Name).OfType<IPropertySymbol>().SingleOrDefault() is not { IsStatic: false } clrProperty) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
propertyDescription.AddPropertyWrapper(clrProperty); |
|||
clrPropertyWrapCandidates.Add((clrProperty, propertyDescription)); |
|||
|
|||
var current = ownerType.BaseType; |
|||
while (current != null) |
|||
{ |
|||
foreach (var otherProp in nameMatches.Where(t => t.owners.Contains(current)).Select(t => t.property)) |
|||
{ |
|||
clrPropertyWrapCandidates.Add((clrProperty, otherProp)); |
|||
} |
|||
|
|||
current = current.BaseType; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
// convert our dictionaries to immutable form
|
|||
_clrPropertyToAvaloniaProperties = clrPropertyWrapCandidates.ToLookup(t => t.Item1, t => t.Item2, SymbolEqualityComparer<IPropertySymbol>.Default) |
|||
.ToImmutableDictionary(g => g.Key, g => g.Distinct().ToImmutableArray(), SymbolEqualityComparer<IPropertySymbol>.Default); |
|||
_avaloniaPropertyDescriptions = propertyDescriptions.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.Seal(), SymbolEqualityComparer.Default); |
|||
|
|||
void RegisterAssignment(ISymbol target, IOperation value) |
|||
{ |
|||
switch (ResolveOperationSource(value)) |
|||
{ |
|||
case IInvocationOperation invocation: |
|||
RegisterInitializer_Invocation(invocation, target, propertyDescriptions); |
|||
break; |
|||
case IFieldReferenceOperation fieldRef when IsAvaloniaPropertyStorage(fieldRef.Field): |
|||
fieldInitializations[fieldRef.Field] = target; |
|||
break; |
|||
case IPropertyReferenceOperation propRef when IsAvaloniaPropertyStorage(propRef.Property): |
|||
fieldInitializations[propRef.Property] = target; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
(SyntaxNode, SemanticModel) GetNodeAndModel(SyntaxReference syntaxRef) => |
|||
(syntaxRef.GetSyntax(cancellationToken), semanticModels.GetOrAdd(syntaxRef.SyntaxTree, st => compilation.GetSemanticModel(st))); |
|||
} |
|||
|
|||
// This method handles registration of a new AvaloniaProperty, and calls to AddOwner.
|
|||
private void RegisterInitializer_Invocation(IInvocationOperation invocation, ISymbol target, ConcurrentDictionary<ISymbol, AvaloniaPropertyDescription> propertyDescriptions) |
|||
{ |
|||
try |
|||
{ |
|||
if (invocation.TargetMethod.ReturnType is not INamedTypeSymbol propertyType) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var originalMethod = invocation.TargetMethod.OriginalDefinition; |
|||
|
|||
if (_avaloniaPropertyRegisterMethods.Contains(originalMethod)) // This is a call to one of the AvaloniaProperty.Register* methods
|
|||
{ |
|||
TypeReference ownerTypeRef; |
|||
|
|||
if (_ownerTypeParams.TryGetValue(originalMethod, out var ownerTypeParam)) |
|||
{ |
|||
ownerTypeRef = TypeReference.FromInvocationTypeParameter(invocation, ownerTypeParam); |
|||
} |
|||
else if (_ownerParams.TryGetValue(originalMethod, out var ownerParam) && // try extracting the runtime argument
|
|||
ResolveOperationSource(invocation.Arguments[ownerParam.Ordinal].Value) is ITypeOfOperation { Type: ITypeSymbol type } typeOf) |
|||
{ |
|||
ownerTypeRef = new TypeReference(type, typeOf.Syntax.GetLocation()); |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
TypeReference valueTypeRef; |
|||
if (_valueTypeParams.TryGetValue(originalMethod, out var valueTypeParam)) |
|||
{ |
|||
valueTypeRef = TypeReference.FromInvocationTypeParameter(invocation, valueTypeParam); |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
string name; |
|||
switch (ResolveOperationSource(invocation.Arguments[0].Value)) |
|||
{ |
|||
case ILiteralOperation literal when SymbolEquals(literal.Type, _stringType): |
|||
name = (string)literal.ConstantValue.Value!; |
|||
break; |
|||
case INameOfOperation nameof when nameof.Argument is IPropertyReferenceOperation propertyReference: |
|||
name = propertyReference.Property.Name; |
|||
break; |
|||
case IFieldReferenceOperation fieldRef when SymbolEquals(fieldRef.Type, _stringType) && fieldRef.ConstantValue is { HasValue: true } constantValue: |
|||
name = (string)fieldRef.ConstantValue.Value!; |
|||
break; |
|||
default: |
|||
return; |
|||
} |
|||
|
|||
var inherits = false; |
|||
if (_inheritsParams.TryGetValue(originalMethod, out var inheritsParam) && |
|||
invocation.Arguments[inheritsParam.Ordinal].Value is ILiteralOperation literalOp && |
|||
literalOp.ConstantValue.Value is bool constValue) |
|||
{ |
|||
inherits = constValue; |
|||
} |
|||
|
|||
TypeReference? hostTypeRef = null; |
|||
if (SymbolEquals(propertyType.OriginalDefinition, _attachedPropertyType)) |
|||
{ |
|||
if (_hostTypeParams.TryGetValue(originalMethod, out var hostTypeParam)) |
|||
{ |
|||
hostTypeRef = TypeReference.FromInvocationTypeParameter(invocation, hostTypeParam); |
|||
} |
|||
else |
|||
{ |
|||
hostTypeRef = new(_avaloniaObjectType, Location.None); |
|||
} |
|||
} |
|||
|
|||
var description = propertyDescriptions.GetOrAdd(target, s => new AvaloniaPropertyDescription(name, propertyType, valueTypeRef.Type)); |
|||
description.Name = name; |
|||
description.HostType = hostTypeRef; |
|||
description.Inherits = inherits; |
|||
description.SetAssignment(target, ownerTypeRef); |
|||
description.AddOwner(ownerTypeRef); |
|||
} |
|||
else if (_avaloniaPropertyAddOwnerMethods.Contains(invocation.TargetMethod.OriginalDefinition)) // This is a call to one of the AddOwner methods
|
|||
{ |
|||
if (!_ownerTypeParams.TryGetValue(invocation.TargetMethod.OriginalDefinition, out var ownerTypeParam)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (GetReferencedFieldOrProperty(invocation.Instance) is not { } sourceSymbol) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var description = propertyDescriptions[target] = propertyDescriptions.GetOrAdd(sourceSymbol, s => |
|||
{ |
|||
string inferredName = s.Name; |
|||
|
|||
var match = Regex.Match(s.Name, "(?<name>.*)Property$"); |
|||
if (match.Success) |
|||
{ |
|||
inferredName = match.Groups["name"].Value; |
|||
} |
|||
|
|||
if (!_propertyValueTypeParams.TryGetValue(propertyType.OriginalDefinition, out var propertyValueType)) |
|||
{ |
|||
throw new InvalidOperationException($"{propertyType} is not a recognised AvaloniaProperty ({_styledPropertyType}, {_attachedPropertyType}, {_directPropertyType})."); |
|||
} |
|||
|
|||
var valueType = propertyType.TypeArguments[propertyValueType.Ordinal]; |
|||
|
|||
TypeReference? hostTypeRef = null; |
|||
if (SymbolEquals(propertyType.OriginalDefinition, _attachedPropertyType)) |
|||
{ |
|||
hostTypeRef = new(_avaloniaObjectType, Location.None); // assume that an attached property applies everywhere until we find its registration
|
|||
} |
|||
|
|||
var result = new AvaloniaPropertyDescription(inferredName, propertyType, valueType) { HostType = hostTypeRef }; |
|||
|
|||
// assume that the property is owned by its containing type at the point of assignment, until we find its registration
|
|||
result.SetAssignment(s, new(s.ContainingType, Location.None)); |
|||
|
|||
return result; |
|||
}); |
|||
|
|||
var ownerTypeRef = TypeReference.FromInvocationTypeParameter(invocation, ownerTypeParam); |
|||
description.SetAssignment(target, ownerTypeRef); |
|||
description.AddOwner(ownerTypeRef); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AvaloniaAnalysisException($"Failed to register the initializer of '{target}'.", ex); |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="InappropriatePropertyAssignment"/>
|
|||
private void AnalyzeFieldInitializer(OperationAnalysisContext context) |
|||
{ |
|||
var operation = (IFieldInitializerOperation)context.Operation; |
|||
|
|||
foreach (var field in operation.InitializedFields) |
|||
{ |
|||
try |
|||
{ |
|||
if (!_avaloniaPropertyDescriptions.TryGetValue(field, out var description)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (!IsValidAvaloniaPropertyStorage(field)) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(InappropriatePropertyAssignment, field.Locations[0], field)); |
|||
} |
|||
|
|||
AnalyzeInitializer_Shared(context, field, description); |
|||
|
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AvaloniaAnalysisException($"Failed to process initialization of field '{field}'.", ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="InappropriatePropertyAssignment"/>
|
|||
private void AnalyzePropertyInitializer(OperationAnalysisContext context) |
|||
{ |
|||
var operation = (IPropertyInitializerOperation)context.Operation; |
|||
|
|||
foreach (var property in operation.InitializedProperties) |
|||
{ |
|||
try |
|||
{ |
|||
if (!_avaloniaPropertyDescriptions.TryGetValue(property, out var description)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (!IsValidAvaloniaPropertyStorage(property)) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(InappropriatePropertyAssignment, property.Locations[0], property)); |
|||
} |
|||
|
|||
AnalyzeInitializer_Shared(context, property, description); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AvaloniaAnalysisException($"Failed to process initialization of property '{property}'.", ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="InappropriatePropertyAssignment"/>
|
|||
private void AnalyzePropertyStorageAssignment(OperationAnalysisContext context) |
|||
{ |
|||
var operation = (IAssignmentOperation)context.Operation; |
|||
|
|||
try |
|||
{ |
|||
var (target, isValid) = ResolveOperationSource(operation.Target) switch |
|||
{ |
|||
IFieldReferenceOperation fieldRef => (fieldRef.Field, IsValidAvaloniaPropertyStorage(fieldRef.Field)), |
|||
IPropertyReferenceOperation propertyRef => (propertyRef.Property, IsValidAvaloniaPropertyStorage(propertyRef.Property)), |
|||
_ => (default(ISymbol), false), |
|||
}; |
|||
|
|||
if (target == null || !_avaloniaPropertyDescriptions.TryGetValue(target, out var description)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!isValid) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(InappropriatePropertyAssignment, target.Locations[0], target)); |
|||
} |
|||
|
|||
AnalyzeInitializer_Shared(context, target, description); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AvaloniaAnalysisException($"Failed to process assignment '{operation}'.", ex); |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="PropertyNameMismatch"/>
|
|||
/// <seealso cref="OwnerDoesNotMatchOuterType"/>
|
|||
private void AnalyzeInitializer_Shared(OperationAnalysisContext context, ISymbol assignmentSymbol, AvaloniaPropertyDescription description) |
|||
{ |
|||
if (!assignmentSymbol.Name.Contains(description.Name) && assignmentSymbol.DeclaredAccessibility != Accessibility.Private) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(PropertyNameMismatch, assignmentSymbol.Locations[0], |
|||
description.Name, assignmentSymbol)); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var ownerType = description.AssignedTo[assignmentSymbol]; |
|||
|
|||
if (ownerType.Type.TypeKind != TypeKind.Error && |
|||
!IsAvaloniaPropertyType(description.PropertyType, _attachedPropertyType) && |
|||
!SymbolEquals(ownerType.Type, assignmentSymbol.ContainingType)) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(OwnerDoesNotMatchOuterType, ownerType.Location, ownerType.Type)); |
|||
} |
|||
} |
|||
catch (KeyNotFoundException) |
|||
{ |
|||
throw new KeyNotFoundException($"Assignment operation for {assignmentSymbol} was not recorded."); |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="SettingOwnStyledPropertyValue"/>
|
|||
private void AnalyzePropertyWrapperAssignment(OperationAnalysisContext context) |
|||
{ |
|||
var operation = (IAssignmentOperation)context.Operation; |
|||
|
|||
if (ResolveOperationSource(operation) is IParameterReferenceOperation && context.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.Constructor }) |
|||
{ |
|||
// We can consider `new MyType(myValue)` functionally equivalent to `new MyType() { Value = myValue }`. Both set a local value with an external parameter.
|
|||
return; |
|||
} |
|||
|
|||
if (ResolveOperationTarget(operation) is IPropertyReferenceOperation propertyRef && |
|||
propertyRef.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } && |
|||
_clrPropertyToAvaloniaProperties.TryGetValue(propertyRef.Property, out var propertyDescriptions) && |
|||
propertyDescriptions.Any(p => !SymbolEquals(p.PropertyType.OriginalDefinition, _directPropertyType))) |
|||
{ |
|||
if (DerivesFrom(propertyRef.Instance.Type, _userControlType) || DerivesFrom(propertyRef.Instance.Type, _topLevelType)) |
|||
{ |
|||
// Special case: don't warn about local value assignment on a UserControl or TopLevel type.
|
|||
// 1. We don't want to annoy new users, who start with these two types and don't understand binding priorities yet
|
|||
// 2. Such controls either have no consumers, or are treated largely as a black box (i.e. no styles setting dynamic values)
|
|||
return; |
|||
} |
|||
|
|||
context.ReportDiagnostic(Diagnostic.Create(SettingOwnStyledPropertyValue, operation.Syntax.GetLocation())); |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="UnexpectedPropertyAccess"/>
|
|||
/// <seealso cref="InappropriatePropertyRegistration"/>
|
|||
/// <seealso cref="PropertyOwnedByGenericType"/>
|
|||
private void AnalyzeMethodInvocation(OperationAnalysisContext context) |
|||
{ |
|||
var invocation = (IInvocationOperation)context.Operation; |
|||
|
|||
var originalMethod = invocation.TargetMethod.OriginalDefinition; |
|||
|
|||
if (_allGetSetMethods.Contains(originalMethod)) |
|||
{ |
|||
if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } && |
|||
GetReferencedProperty(invocation.Arguments[0]) is { } refProp && |
|||
refProp.description.AssignedTo.TryGetValue(refProp.storageSymbol, out var ownerType) && |
|||
!DerivesFrom(context.ContainingSymbol.ContainingType, ownerType.Type) && |
|||
!DerivesFrom(context.ContainingSymbol.ContainingType, refProp.description.HostType?.Type)) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(UnexpectedPropertyAccess, invocation.Arguments[0].Syntax.GetLocation(), |
|||
refProp.storageSymbol, context.ContainingSymbol.ContainingType)); |
|||
} |
|||
} |
|||
else if (_allAvaloniaPropertyMethods.Contains(originalMethod)) |
|||
{ |
|||
if (!IsStaticConstructorOrInitializer()) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(InappropriatePropertyRegistration, invocation.Syntax.GetLocation(), |
|||
originalMethod.ToDisplayString(TypeQualifiedName))); |
|||
} |
|||
|
|||
if (_ownerTypeParams.TryGetValue(invocation.TargetMethod.OriginalDefinition, out var typeParam) && |
|||
invocation.TargetMethod.TypeArguments[typeParam.Ordinal] is { } newOwnerType) |
|||
{ |
|||
if (newOwnerType is INamedTypeSymbol { IsGenericType: true }) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(PropertyOwnedByGenericType, TypeReference.FromInvocationTypeParameter(invocation, typeParam).Location)); |
|||
} |
|||
|
|||
if (_avaloniaPropertyAddOwnerMethods.Contains(originalMethod) && GetReferencedProperty(invocation.Instance!) is { } refProp) |
|||
{ |
|||
var ownerMatches = refProp.description.AssignedTo.Where(kvp => !SymbolEquals(kvp.Key, context.ContainingSymbol) && DerivesFrom(newOwnerType, kvp.Value.Type)).ToArray(); |
|||
|
|||
if (ownerMatches.Any()) |
|||
{ |
|||
var ownerMatchesExceptBaseTypes = ownerMatches.Where(m => !DerivesFrom(context.ContainingSymbol.ContainingType, m.Key.ContainingType, includeSelf: false)).ToArray(); |
|||
var routesMessage = ownerMatchesExceptBaseTypes.Length switch |
|||
{ |
|||
0 => "its base type", |
|||
1 => ownerMatchesExceptBaseTypes.Single().Key.ToString(), |
|||
_ => $"{ownerMatches.Length} routes\n\t{string.Join("\n\t", ownerMatches.Select(kvp => kvp.Key))}" |
|||
}; |
|||
|
|||
context.ReportDiagnostic(Diagnostic.Create(SuperfluousAddOwnerCall, invocation.Syntax.GetLocation(), ownerMatches.Select(kvp => kvp.Value.Location), |
|||
newOwnerType, refProp.storageSymbol, routesMessage)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool IsStaticConstructorOrInitializer() => |
|||
context.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.StaticConstructor } || |
|||
ResolveOperationTarget(invocation.Parent!) switch |
|||
{ |
|||
IFieldInitializerOperation fieldInit when fieldInit.InitializedFields.All(f => f.IsStatic) => true, |
|||
IPropertyInitializerOperation propInit when propInit.InitializedProperties.All(p => p.IsStatic) => true, |
|||
_ => false, |
|||
}; |
|||
} |
|||
|
|||
private (AvaloniaPropertyDescription description, ISymbol storageSymbol)? GetReferencedProperty(IOperation operation) |
|||
{ |
|||
if (GetReferencedFieldOrProperty(operation) is { } storageSymbol && _avaloniaPropertyDescriptions.TryGetValue(storageSymbol, out var result)) |
|||
{ |
|||
return (result, storageSymbol); |
|||
} |
|||
else |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="AmbiguousPropertyName"/>
|
|||
/// <seealso cref="PropertyTypeMismatch"/>
|
|||
/// <seealso cref="AssociatedAvaloniaProperty"/>
|
|||
/// <seealso cref="InconsistentAccessibility"/>
|
|||
/// <seealso cref="MissingAccessor"/>
|
|||
private void AnalyzeWrapperCrlProperty(SymbolAnalysisContext context) |
|||
{ |
|||
var property = (IPropertySymbol)context.Symbol; |
|||
|
|||
if (!_clrPropertyToAvaloniaProperties.TryGetValue(property, out var candidateTargetProperties)) |
|||
{ |
|||
return; // does not refer to an AvaloniaProperty
|
|||
} |
|||
|
|||
try |
|||
{ |
|||
if (candidateTargetProperties.Length > 1) |
|||
{ |
|||
var candidateSymbols = candidateTargetProperties.Select(d => d.ClosestAssignmentFor(property.ContainingType)).Where(s => s != null).OrderBy(s => s!.Name); |
|||
context.ReportDiagnostic(Diagnostic.Create(AmbiguousPropertyName, property.Locations[0], candidateSymbols.SelectMany(s => s!.Locations), |
|||
property.ContainingType, property.Name, $"\n\t{string.Join("\n\t", candidateSymbols)}")); |
|||
return; |
|||
} |
|||
|
|||
var avaloniaPropertyDescription = candidateTargetProperties[0]; |
|||
var avaloniaPropertyStorage = avaloniaPropertyDescription.ClosestAssignmentFor(property.ContainingType); |
|||
|
|||
if (avaloniaPropertyStorage == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
context.ReportDiagnostic(Diagnostic.Create(AssociatedAvaloniaProperty, property.Locations[0], new[] { avaloniaPropertyStorage.Locations[0] }, |
|||
avaloniaPropertyDescription.PropertyType.Name, avaloniaPropertyStorage)); |
|||
|
|||
if (!SymbolEquals(property.Type, avaloniaPropertyDescription.ValueType, includeNullability: true)) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(PropertyTypeMismatch, property.Locations[0], |
|||
avaloniaPropertyStorage, $"\t\n{string.Join("\t\n", avaloniaPropertyDescription.ValueType, property.Type)}")); |
|||
} |
|||
|
|||
if (property.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(InconsistentAccessibility, property.Locations[0], "property", avaloniaPropertyStorage)); |
|||
} |
|||
|
|||
VerifyAccessor(property.GetMethod, "readable", "get"); |
|||
|
|||
if (!IsAvaloniaPropertyType(avaloniaPropertyDescription.PropertyType, _directPropertyType)) |
|||
{ |
|||
VerifyAccessor(property.SetMethod, "writeable", "set"); |
|||
} |
|||
|
|||
void VerifyAccessor(IMethodSymbol? method, string verb, string methodName) |
|||
{ |
|||
if (method == null) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName)); |
|||
} |
|||
else if (method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility) |
|||
{ |
|||
context.ReportDiagnostic(Diagnostic.Create(InconsistentAccessibility, method.Locations[0], "property accessor", avaloniaPropertyStorage)); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AvaloniaAnalysisException($"Failed to analyse wrapper property '{property}'.", ex); |
|||
} |
|||
} |
|||
|
|||
/// <seealso cref="AccessorSideEffects"/>
|
|||
private void AnalyzePropertyMethods(CodeBlockAnalysisContext context) |
|||
{ |
|||
if (context.OwningSymbol is not IMethodSymbol { AssociatedSymbol: IPropertySymbol property } method) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
if (!_clrPropertyToAvaloniaProperties.TryGetValue(property, out var candidateTargetProperties) || |
|||
candidateTargetProperties.Length != 1) // a diagnostic about multiple candidates will have already been reported
|
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var avaloniaPropertyDescription = candidateTargetProperties.Single(); |
|||
|
|||
if (IsAvaloniaPropertyType(avaloniaPropertyDescription.PropertyType, _directPropertyType)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!SymbolEquals(property.Type, avaloniaPropertyDescription.ValueType)) |
|||
{ |
|||
return; // a diagnostic about this will have already been reported, and if the cast is implicit then this message would be confusing anyway
|
|||
} |
|||
|
|||
var bodyNode = context.CodeBlock.ChildNodes().Single(); |
|||
|
|||
var operation = bodyNode.DescendantNodes() |
|||
.Where(n => n.IsKind(SyntaxKind.InvocationExpression)) // this line is specific to C#
|
|||
.Select(n => (IInvocationOperation)context.SemanticModel.GetOperation(n)!) |
|||
.FirstOrDefault(); |
|||
|
|||
var isGetMethod = method.MethodKind == MethodKind.PropertyGet; |
|||
|
|||
var expectedInvocations = isGetMethod ? _getValueMethods : _setValueMethods; |
|||
|
|||
if (operation == null || bodyNode.ChildNodes().Count() != 1 || !expectedInvocations.Contains(operation.TargetMethod.OriginalDefinition)) |
|||
{ |
|||
ReportSideEffects(); |
|||
return; |
|||
} |
|||
|
|||
if (operation.Arguments.Length != 0) |
|||
{ |
|||
switch (ResolveOperationSource(operation.Arguments[0].Value)) |
|||
{ |
|||
case IFieldReferenceOperation fieldRef when avaloniaPropertyDescription.AssignedTo.ContainsKey(fieldRef.Field): |
|||
case IPropertyReferenceOperation propertyRef when avaloniaPropertyDescription.AssignedTo.ContainsKey(propertyRef.Property): |
|||
break; // the argument is a reference to the correct AvaloniaProperty object
|
|||
default: |
|||
ReportSideEffects(operation.Arguments[0].Value.Syntax.GetLocation()); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (!isGetMethod && |
|||
operation.Arguments.Length >= 2 && |
|||
operation.Arguments[1].Value.Kind != OperationKind.ParameterReference) // passing something other than `value` to SetValue
|
|||
{ |
|||
ReportSideEffects(operation.Arguments[1].Syntax.GetLocation()); |
|||
} |
|||
|
|||
void ReportSideEffects(Location? locationOverride = null) |
|||
{ |
|||
var propertySourceName = avaloniaPropertyDescription.ClosestAssignmentFor(method.ContainingType)?.Name ?? "[unknown]"; |
|||
|
|||
context.ReportDiagnostic(Diagnostic.Create(AccessorSideEffects, locationOverride ?? context.CodeBlock.GetLocation(), |
|||
avaloniaPropertyDescription.Name, |
|||
isGetMethod ? "read" : "written to", |
|||
isGetMethod ? "get" : "set", |
|||
isGetMethod ? $"GetValue({propertySourceName})" : $"SetValue({propertySourceName}, value)")); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AvaloniaAnalysisException($"Failed to process property accessor '{method}'.", ex); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,465 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Collections.ObjectModel; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using System.Runtime.Serialization; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
using Microsoft.CodeAnalysis.Diagnostics; |
|||
using Microsoft.CodeAnalysis.Operations; |
|||
|
|||
namespace Avalonia.Analyzers; |
|||
|
|||
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] |
|||
[SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:Enable analyzer release tracking")] |
|||
public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer |
|||
{ |
|||
private const string Category = "AvaloniaProperty"; |
|||
|
|||
private const string TypeMismatchTag = "TypeMismatch"; |
|||
private const string NameCollisionTag = "NameCollision"; |
|||
private const string AssociatedClrPropertyTag = "AssociatedClrProperty"; |
|||
private const string InappropriateReadWriteTag = "InappropriateReadWrite"; |
|||
|
|||
private static readonly DiagnosticDescriptor AssociatedAvaloniaProperty = new( |
|||
"AVP0001", |
|||
"Identification of the AvaloniaProperty associated with a CLR property", |
|||
"Associated AvaloniaProperty: {0} {1}", |
|||
Category, |
|||
DiagnosticSeverity.Info, |
|||
isEnabledByDefault: false, |
|||
"This informational diagnostic identifies which AvaloniaProperty a CLR property is associated with.", |
|||
AssociatedClrPropertyTag); |
|||
|
|||
private static readonly DiagnosticDescriptor InappropriatePropertyAssignment = new( |
|||
"AVP1000", |
|||
"AvaloniaProperty objects should be stored appropriately", |
|||
"Incorrect AvaloniaProperty storage: {0} should be static and readonly", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"AvaloniaProperty objects have static lifetimes and should be stored accordingly."); |
|||
|
|||
private static readonly DiagnosticDescriptor InappropriatePropertyRegistration = new( |
|||
"AVP1001", |
|||
"The same AvaloniaProperty should not be registered twice", |
|||
"Unsafe registration: {0} should be called only in static constructors or static initializers", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"AvaloniaProperty objects have static lifetimes and should be created only once. To ensure this, only call Register or AddOwner in static constructors or static initializers."); |
|||
|
|||
private static readonly DiagnosticDescriptor PropertyOwnedByGenericType = new( |
|||
"AVP1002", |
|||
"AvaloniaProperty objects should not be owned by a generic type", |
|||
"Inadvisable registration: Generic types cannot be referenced from XAML. Create a non-generic type to be the owner of this AvaloniaProperty.", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"It is sometimes necessary to refer to an AvaloniaProperty in XAML by providing its class name. This cannot be achieved if property's owner is a generic type." + |
|||
" Additionally, a new AvaloniaProperty object will be generated each time a new version of the generic owner type is constructed, which may be unexpected."); |
|||
|
|||
private static readonly DiagnosticDescriptor OwnerDoesNotMatchOuterType = new( |
|||
"AVP1010", |
|||
"AvaloniaProperty objects should be owned by the type in which they are stored", |
|||
"Type mismatch: AvaloniaProperty owner is {0}, which is not the containing type", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"The owner of an AvaloniaProperty should generally be the containing type. This ensures that the property can be used as expected in XAML.", |
|||
TypeMismatchTag); |
|||
|
|||
private static readonly DiagnosticDescriptor UnexpectedPropertyAccess = new( |
|||
"AVP1011", |
|||
"An AvaloniaObject should own each AvaloniaProperty it reads or writes on itself", |
|||
"Unexpected property use: {0} is neither owned by nor attached to {1}", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"It is possible to use any AvaloniaProperty with any AvaloniaObject. However, each AvaloniaProperty an object uses on itself should be either owned by that object, or attached to that object.", |
|||
InappropriateReadWriteTag); |
|||
|
|||
private static readonly DiagnosticDescriptor SettingOwnStyledPropertyValue = new( |
|||
"AVP1012", |
|||
"An AvaloniaObject should use SetCurrentValue when assigning its own StyledProperty or AttachedProperty values", |
|||
"Inappropriate assignment: An AvaloniaObject should use SetCurrentValue when setting its own StyledProperty or AttachedProperty values", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"The standard means of setting an AvaloniaProperty is to call the SetValue method (often via a CLR property setter). This will forcibly overwrite values from sources like styles and templates, " + |
|||
"which is something that should only be done by consumers of the control, not the control itself. Controls which want to set their own values should instead call the SetCurrentValue method, or " + |
|||
"refactor the property into a DirectProperty. An assignment is exempt from this diagnostic in two scenarios: when it is forwarding a constructor parameter, and when the target object is derived " + |
|||
"from UserControl or TopLevel.", |
|||
InappropriateReadWriteTag); |
|||
|
|||
private static readonly DiagnosticDescriptor SuperfluousAddOwnerCall = new( |
|||
"AVP1013", |
|||
"AvaloniaProperty owners should not be added superfluously", |
|||
"Superfluous owner: {0} is already an owner of {1} via {2}", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"Ownership of an AvaloniaProperty is inherited along the type hierarchy. There is no need for a derived type to assert ownership over a base type's properties. This diagnostic can be a symptom of an incorrect property owner elsewhere.", |
|||
InappropriateReadWriteTag); |
|||
|
|||
private static readonly DiagnosticDescriptor DuplicatePropertyName = new( |
|||
"AVP1020", |
|||
"AvaloniaProperty names should be unique within each class", |
|||
"Name collision: {0} has the same name as {1}", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"Querying for an AvaloniaProperty by name requires that each property associated with a type have a unique name.", |
|||
NameCollisionTag); |
|||
|
|||
private static readonly DiagnosticDescriptor AmbiguousPropertyName = new( |
|||
"AVP1021", |
|||
"There should be an unambiguous relationship between the CLR properties and Avalonia properties of a class", |
|||
"Name collision: {0} owns multiple Avalonia properties with the name '{1}' {2}", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"It is unclear which AvaloniaProperty this CLR property refers to. Ensure that each AvaloniaProperty associated with a type has a unique name. If you need to change behaviour of a base property in your class, call its OverrideMetadata or OverrideDefaultValue methods.", |
|||
NameCollisionTag); |
|||
|
|||
private static readonly DiagnosticDescriptor PropertyNameMismatch = new( |
|||
"AVP1022", |
|||
"An AvaloniaProperty object should be stored in a field or CLR property which reflects its name", |
|||
"Bad name: An AvaloniaProperty named '{0}' is being assigned to {1}. These names do not relate.", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"An AvaloniaProperty should be stored in a field or property which contains its name. For example, a property named \"Brush\" should be assigned to a field called \"BrushProperty\".\nPrivate symbols are exempt from this diagnostic.", |
|||
NameCollisionTag); |
|||
|
|||
private static readonly DiagnosticDescriptor AccessorSideEffects = new( |
|||
"AVP1030", |
|||
"StyledProperty accessors should not have side effects", |
|||
"Side effects: '{0}' is an AvaloniaProperty which can be {1} without the use of this CLR property. This {2} accessor should do nothing except call {3}.", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call any user CLR properties. To execute code before or after the property is set, consider: 1) adding a Coercion method, b) adding a static observer with AvaloniaProperty.Changed.AddClassHandler, and/or c) overriding the AvaloniaObject.OnPropertyChanged method.", |
|||
AssociatedClrPropertyTag); |
|||
|
|||
private static readonly DiagnosticDescriptor MissingAccessor = new( |
|||
"AVP1031", |
|||
"A CLR property should support the same get/set operations as its associated AvaloniaProperty", |
|||
"Missing accessor: {0} is {1}, but this CLR property lacks a {2} accessor", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. Not providing both CLR property accessors is ineffective.", |
|||
AssociatedClrPropertyTag); |
|||
|
|||
private static readonly DiagnosticDescriptor InconsistentAccessibility = new( |
|||
"AVP1032", |
|||
"A CLR property and its accessors should be equally accessible as its associated AvaloniaProperty", |
|||
"Inconsistent accessibility: CLR {0} accessibility does not match accessibility of {1}", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. Defining a CLR property with different accessibility from its associated AvaloniaProperty is ineffective.", |
|||
AssociatedClrPropertyTag); |
|||
|
|||
private static readonly DiagnosticDescriptor PropertyTypeMismatch = new( |
|||
"AVP1040", |
|||
"A CLR property type should match the associated AvaloniaProperty type", |
|||
"Type mismatch: CLR property type differs from the value type of {0} {1}", |
|||
Category, |
|||
DiagnosticSeverity.Warning, |
|||
isEnabledByDefault: true, |
|||
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. A CLR property changing the value type (even when an implicit cast is possible) is ineffective and can lead to InvalidCastException to be thrown.", |
|||
TypeMismatchTag, AssociatedClrPropertyTag); |
|||
|
|||
private static readonly SymbolDisplayFormat TypeQualifiedName = new( |
|||
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, |
|||
memberOptions: SymbolDisplayMemberOptions.IncludeContainingType); |
|||
|
|||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create( |
|||
AssociatedAvaloniaProperty, |
|||
InappropriatePropertyAssignment, |
|||
InappropriatePropertyRegistration, |
|||
PropertyOwnedByGenericType, |
|||
OwnerDoesNotMatchOuterType, |
|||
UnexpectedPropertyAccess, |
|||
SettingOwnStyledPropertyValue, |
|||
SuperfluousAddOwnerCall, |
|||
DuplicatePropertyName, |
|||
AmbiguousPropertyName, |
|||
PropertyNameMismatch, |
|||
AccessorSideEffects, |
|||
MissingAccessor, |
|||
InconsistentAccessibility, |
|||
PropertyTypeMismatch); |
|||
|
|||
public override void Initialize(AnalysisContext context) |
|||
{ |
|||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); |
|||
context.EnableConcurrentExecution(); |
|||
|
|||
context.RegisterCompilationStartAction(c => |
|||
{ |
|||
if (c.Compilation.GetTypeByMetadataName("Avalonia.AvaloniaObject") is { } avaloniaObjectType) |
|||
{ |
|||
new CompileAnalyzer(c, avaloniaObjectType); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static bool IsAvaloniaPropertyType(ITypeSymbol type, params INamedTypeSymbol[] propertyTypes) => IsAvaloniaPropertyType(type, propertyTypes.AsEnumerable()); |
|||
|
|||
private static bool IsAvaloniaPropertyType(ITypeSymbol type, IEnumerable<INamedTypeSymbol> propertyTypes) |
|||
{ |
|||
type = type.OriginalDefinition; |
|||
|
|||
return propertyTypes.Any(t => SymbolEquals(type, t)); |
|||
} |
|||
|
|||
private static bool DerivesFrom(ITypeSymbol? type, ITypeSymbol? baseType, bool includeSelf = true) |
|||
{ |
|||
if (baseType != null) |
|||
{ |
|||
if (!includeSelf) |
|||
{ |
|||
type = type?.BaseType; |
|||
} |
|||
|
|||
while (type != null) |
|||
{ |
|||
if (SymbolEquals(type, baseType)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
type = type.BaseType; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Follows assignments and conversions back to their source.
|
|||
/// </summary>
|
|||
private static IOperation ResolveOperationSource(IOperation operation) |
|||
{ |
|||
while (true) |
|||
{ |
|||
switch (operation) |
|||
{ |
|||
case IConversionOperation conversion: |
|||
operation = conversion.Operand; |
|||
break; |
|||
case ISimpleAssignmentOperation assignment: |
|||
operation = assignment.Value; |
|||
break; |
|||
default: |
|||
return operation; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static IOperation ResolveOperationTarget(IOperation operation) |
|||
{ |
|||
while (true) |
|||
{ |
|||
switch (operation) |
|||
{ |
|||
case IConversionOperation conversion: |
|||
operation = conversion.Parent!; |
|||
break; |
|||
case ISimpleAssignmentOperation assignment: |
|||
operation = assignment.Target; |
|||
break; |
|||
default: |
|||
return operation; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static ISymbol? GetReferencedFieldOrProperty(IOperation? operation) => operation == null ? null : ResolveOperationSource(operation) switch |
|||
{ |
|||
IFieldReferenceOperation fieldRef => fieldRef.Field, |
|||
IPropertyReferenceOperation propertyRef => propertyRef.Property, |
|||
IArgumentOperation argument => GetReferencedFieldOrProperty(argument.Value), |
|||
_ => null, |
|||
}; |
|||
|
|||
private static bool IsValidAvaloniaPropertyStorage(IFieldSymbol field) => field.IsStatic && field.IsReadOnly; |
|||
private static bool IsValidAvaloniaPropertyStorage(IPropertySymbol field) => field.IsStatic && field.IsReadOnly; |
|||
|
|||
private static bool SymbolEquals(ISymbol? x, ISymbol? y, bool includeNullability = false) |
|||
{ |
|||
// The current version of Microsoft.CodeAnalysis includes an "IncludeNullability" comparer,
|
|||
// but it overshoots the target and tries to compare EVERYTHING. This leads to two symbols for
|
|||
// the same type not being equal if they were imported into different compile units (i.e. assemblies).
|
|||
// So for now, we will just discard this parameter.
|
|||
_ = includeNullability; |
|||
|
|||
return SymbolEqualityComparer.Default.Equals(x, y); |
|||
} |
|||
|
|||
private class AvaloniaPropertyDescription |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the name that was assigned to this property when it was registered.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If the property was not registered within the current compile context, this value will be inferred from
|
|||
/// the name of the field (or CLR property) in which the AvaloniaProperty object is stored.
|
|||
/// </remarks>
|
|||
public string Name { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the type of the AvaloniaProperty itself: Styled, Direct, or Attached
|
|||
/// </summary>
|
|||
public INamedTypeSymbol PropertyType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the TValue type that the property stores.
|
|||
/// </summary>
|
|||
public ITypeSymbol ValueType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets whether the value of this property is inherited from the parent AvaloniaObject.
|
|||
/// </summary>
|
|||
public bool Inherits { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the type which registered the property, and all types which have added themselves as owners.
|
|||
/// </summary>
|
|||
public IReadOnlyCollection<TypeReference> OwnerTypes { get; private set; } |
|||
private ConcurrentBag<TypeReference>? _ownerTypes = new(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the type to which an AttachedProperty is attached, or null if the property is StyledProperty or DirectProperty.
|
|||
/// </summary>
|
|||
public TypeReference? HostType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a dictionary which maps fields and properties which were initialized with this AvaloniaProperty to the TOwner specified at each assignment.
|
|||
/// </summary>
|
|||
public IReadOnlyDictionary<ISymbol, TypeReference> AssignedTo { get; private set; } |
|||
private ConcurrentDictionary<ISymbol, TypeReference>? _assignedTo = new(SymbolEqualityComparer.Default); |
|||
|
|||
/// <summary>
|
|||
/// Gets properties which provide convenient access to the AvaloniaProperty on an instance of an AvaloniaObject.
|
|||
/// </summary>
|
|||
public IReadOnlyCollection<IPropertySymbol> PropertyWrappers { get; private set; } |
|||
private ConcurrentBag<IPropertySymbol>? _propertyWrappers = new(); |
|||
|
|||
public AvaloniaPropertyDescription(string name, INamedTypeSymbol propertyType, ITypeSymbol valueType) |
|||
{ |
|||
Name = name; |
|||
PropertyType = propertyType; |
|||
ValueType = valueType; |
|||
|
|||
OwnerTypes = _ownerTypes; |
|||
PropertyWrappers = _propertyWrappers; |
|||
AssignedTo = _assignedTo; |
|||
} |
|||
|
|||
private const string SealedError = "PropertyDescription has been sealed."; |
|||
|
|||
public void AddOwner(TypeReference owner) => (_ownerTypes ?? throw new InvalidOperationException(SealedError)).Add(owner); |
|||
|
|||
public void AddPropertyWrapper(IPropertySymbol property) => (_propertyWrappers ?? throw new InvalidOperationException(SealedError)).Add(property); |
|||
|
|||
public void SetAssignment(ISymbol assignmentTarget, TypeReference ownerType) => (_assignedTo ?? throw new InvalidOperationException(SealedError))[assignmentTarget] = ownerType; |
|||
|
|||
public AvaloniaPropertyDescription Seal() |
|||
{ |
|||
if (_ownerTypes == null || _propertyWrappers == null || _assignedTo == null) |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
OwnerTypes = _ownerTypes.ToImmutableHashSet(); |
|||
_ownerTypes = null; |
|||
|
|||
PropertyWrappers = _propertyWrappers.ToImmutableHashSet<IPropertySymbol>(SymbolEqualityComparer.Default); |
|||
_propertyWrappers = null; |
|||
|
|||
AssignedTo = new ReadOnlyDictionary<ISymbol, TypeReference>(_assignedTo); |
|||
_assignedTo = null; |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Searches the inheritance hierarchy of the given type for a field or property to which this AvaloniaProperty is assigned.
|
|||
/// </summary>
|
|||
public ISymbol? ClosestAssignmentFor(ITypeSymbol? type) |
|||
{ |
|||
var assignmentsByType = AssignedTo.Keys.ToLookup(s => s.ContainingType, SymbolEqualityComparer.Default); |
|||
|
|||
while (type != null) |
|||
{ |
|||
if (assignmentsByType.Contains(type)) |
|||
{ |
|||
return assignmentsByType[type].First(); |
|||
} |
|||
type = type.BaseType; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private readonly struct TypeReference |
|||
{ |
|||
public ITypeSymbol Type { get; } |
|||
public Location Location { get; } |
|||
|
|||
public TypeReference(ITypeSymbol type, Location location) |
|||
{ |
|||
Type = type; |
|||
Location = location; |
|||
} |
|||
|
|||
public static TypeReference FromInvocationTypeParameter(IInvocationOperation invocation, ITypeParameterSymbol typeParameter) |
|||
{ |
|||
var argument = invocation.TargetMethod.TypeArguments[typeParameter.Ordinal]; |
|||
|
|||
var typeArgumentSyntax = invocation.Syntax; |
|||
if (invocation.Language == LanguageNames.CSharp) // type arguments do not appear in the invocation, so search the code for them
|
|||
{ |
|||
try |
|||
{ |
|||
typeArgumentSyntax = invocation.Syntax.DescendantNodes() |
|||
.First(n => n.IsKind(SyntaxKind.TypeArgumentList)) |
|||
.DescendantNodes().ElementAt(typeParameter.Ordinal); |
|||
} |
|||
catch |
|||
{ |
|||
// ignore, this is just a nicety
|
|||
} |
|||
} |
|||
|
|||
return new TypeReference(argument, typeArgumentSyntax.GetLocation()); |
|||
} |
|||
} |
|||
|
|||
private class SymbolEqualityComparer<T> : IEqualityComparer<T> where T : ISymbol |
|||
{ |
|||
public bool Equals(T x, T y) => SymbolEqualityComparer.Default.Equals(x, y); |
|||
public int GetHashCode(T obj) => SymbolEqualityComparer.Default.GetHashCode(obj); |
|||
|
|||
public static SymbolEqualityComparer<T> Default { get; } = new(); |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public class AvaloniaAnalysisException : Exception |
|||
{ |
|||
public AvaloniaAnalysisException(string message, Exception? innerException = null) : base(message, innerException) |
|||
{ |
|||
} |
|||
|
|||
protected AvaloniaAnalysisException(SerializationInfo info, StreamingContext context) : base(info, context) |
|||
{ |
|||
} |
|||
} |
|||
Loading…
Reference in new issue