├── .github └── workflows │ └── dotnet-desktop.yml ├── .gitmodules ├── LICENSE ├── README.md ├── xalia-netcore.sln ├── xalia.sln └── xalia ├── App.config ├── AtSpi2 ├── AccessibleProvider.cs ├── ActionProvider.cs ├── ApplicationProvider.cs ├── AtSpiActionList.cs ├── AtSpiAttributes.cs ├── AtSpiConnection.cs ├── AtSpiState.cs ├── AtSpiSupported.cs ├── ComponentProvider.cs ├── DBusUtils.cs ├── SelectionProvider.cs └── ValueProvider.cs ├── GlobalSuppressions.cs ├── Gudl ├── ApplyExpression.cs ├── BinaryExpression.cs ├── DoubleExpression.cs ├── GudlDeclaration.cs ├── GudlExpression.cs ├── GudlParser.cs ├── GudlPrecedence.cs ├── GudlSelector.cs ├── GudlStatement.cs ├── GudlToken.cs ├── GudlTokenizer.cs ├── IdentifierExpression.cs ├── IntegerExpression.cs ├── StringExpression.cs └── UnaryExpression.cs ├── Input ├── InputBackend.cs ├── InputMapping.cs ├── InputQueue.cs ├── InputState.cs ├── InputSystem.cs ├── VirtualInputBackend.cs └── VirtualInputSink.cs ├── Interop ├── Win32.cs ├── Win32RemoteProcessMemory.cs ├── Win32WaitHandle.cs └── X11.cs ├── MainClass.cs ├── Sdl ├── GameControllerInput.cs ├── OverlayBox.cs ├── SdlOverlayBox.cs ├── SdlSynchronizationContext.cs ├── SplitOverlayBox.cs ├── Win32LayeredBox.cs ├── Win32WindowingSystem.cs ├── WindowingSystem.cs ├── X11WindowingSystem.cs └── XdgWindowingSystem.cs ├── Ui ├── CurrentViewRoutine.cs ├── SendKey.cs ├── SendScroll.cs ├── TargetMoveButtonRoutine.cs ├── TargetMoveRoutine.cs └── UiMain.cs ├── UiDom ├── ExpressionWatcher.cs ├── IUiDomApplication.cs ├── IUiDomProvider.cs ├── IUiDomScrollToProvider.cs ├── IUiDomValueProvider.cs ├── UiDomAdjustScrollbars.cs ├── UiDomBoolean.cs ├── UiDomDoAction.cs ├── UiDomDoActionRoutine.cs ├── UiDomDouble.cs ├── UiDomElement.cs ├── UiDomEnum.cs ├── UiDomEnviron.cs ├── UiDomInt.cs ├── UiDomIsRelationship.cs ├── UiDomMapDirections.cs ├── UiDomMethod.cs ├── UiDomOnRelease.cs ├── UiDomProviderBase.cs ├── UiDomRadialDeadzone.cs ├── UiDomRelationship.cs ├── UiDomRelationshipWatcher.cs ├── UiDomRepeatAction.cs ├── UiDomRoot.cs ├── UiDomRoutine.cs ├── UiDomRoutineAsync.cs ├── UiDomRoutinePress.cs ├── UiDomRoutineSequence.cs ├── UiDomRoutineSync.cs ├── UiDomString.cs ├── UiDomUndefined.cs └── UiDomValue.cs ├── Util └── RangeList.cs ├── Utils.cs ├── Viewer ├── UiDomViewer.Designer.cs ├── UiDomViewer.cs ├── UiDomViewer.resx └── UiDomViewerProvider.cs ├── Win32 ├── AccessibleProvider.cs ├── CommandThread.cs ├── ElementIdentifier.cs ├── HwndButtonProvider.cs ├── HwndComboBoxProvider.cs ├── HwndDialogProvider.cs ├── HwndEditProvider.cs ├── HwndHeaderItemProvider.cs ├── HwndHeaderProvider.cs ├── HwndListBoxItemProvider.cs ├── HwndListBoxProvider.cs ├── HwndListBoxScrollProvider.cs ├── HwndListViewCellProvider.cs ├── HwndListViewItemProvider.cs ├── HwndListViewProvider.cs ├── HwndListViewScrollProvider.cs ├── HwndMsaaChildProvider.cs ├── HwndProvider.cs ├── HwndRichEditProvider.cs ├── HwndStaticProvider.cs ├── HwndSysLinkProvider.cs ├── HwndTabItemProvider.cs ├── HwndTabProvider.cs ├── HwndTrackBarProvider.cs ├── IWin32Container.cs ├── IWin32LocationChange.cs ├── IWin32NameChange.cs ├── IWin32ScrollChange.cs ├── IWin32Scrollable.cs ├── IWin32Styles.cs ├── NonclientProvider.cs ├── NonclientScrollProvider.cs ├── UiaProvider.cs ├── Win32Connection.cs ├── Win32ItemRects.cs └── Win32Rect.cs ├── XKeyCodes.cs ├── app.manifest ├── main.gudl ├── xalia-netcore.csproj └── xalia.csproj /.github/workflows/dotnet-desktop.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core Desktop 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | submodules: recursive 21 | 22 | - name: Install .NET Core 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: 8.0.x 26 | 27 | - name: Setup MSBuild.exe 28 | uses: microsoft/setup-msbuild@v2 29 | 30 | - name: Setup Nuget 31 | uses: nuget/setup-nuget@v2 32 | 33 | - name: Fetch SDL 34 | run: curl -L -o sdl.zip "https://github.com/libsdl-org/SDL/releases/download/release-2.30.11/SDL2-2.30.11-win32-x64.zip" 35 | 36 | - name: Extract SDL 37 | run: tar -C xalia -xf sdl.zip 38 | 39 | - name: Restore NuGet Packages 40 | run: nuget restore xalia.sln 41 | 42 | - name: Build net48-mono 43 | run: msbuild xalia.sln /p:Configuration=Release-NetStandard 44 | 45 | - name: Upload net48-mono 46 | uses: actions/upload-artifact@v4 47 | with: 48 | name: net48-mono 49 | path: xalia\bin\Release\** 50 | if-no-files-found: error 51 | 52 | - name: Build netcore-linux-noruntime 53 | run: dotnet publish xalia-netcore.sln --runtime linux-x64 --configuration Release-Linux --no-self-contained 54 | 55 | - name: Build netcore-windows-noruntime 56 | run: dotnet publish xalia-netcore.sln --runtime win-x64 --configuration Release-Windows --no-self-contained 57 | 58 | - name: Upload netcore-linux-noruntime 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: net9-linux-noruntime 62 | path: xalia\bin-netcore-linux\Release-Linux\net9.0\linux-x64\publish\** 63 | if-no-files-found: error 64 | 65 | - name: Upload netcore-windows-noruntime 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: net9-windows-noruntime 69 | path: xalia\bin-netcore-windows\Release-Windows\net9.0-windows\win-x64\publish\** 70 | if-no-files-found: error 71 | 72 | - name: Build netcore-linux 73 | run: dotnet publish xalia-netcore.sln --runtime linux-x64 --configuration Release-Linux --self-contained 74 | 75 | - name: Build netcore-windows 76 | run: dotnet publish xalia-netcore.sln --runtime win-x64 --configuration Release-Windows --self-contained 77 | 78 | - name: Upload netcore-linux 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: net9-linux 82 | path: xalia\bin-netcore-linux\Release-Linux\net9.0\linux-x64\publish\** 83 | if-no-files-found: error 84 | 85 | - name: Upload netcore-windows 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: net9-windows 89 | path: xalia\bin-netcore-windows\Release-Windows\net9.0-windows\win-x64\publish\** 90 | if-no-files-found: error 91 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "xalia/external/SDL2-CS"] 2 | path = xalia/external/SDL2-CS 3 | url = https://github.com/flibitijibibo/SDL2-CS.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Esme Povirk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /xalia-netcore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32516.85 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "xalia-netcore", "xalia\xalia-netcore.csproj", "{1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug-Windows|Any CPU = Debug-Windows|Any CPU 11 | Release-Windows|Any CPU = Release-Windows|Any CPU 12 | Debug-Linux|Any CPU = Debug-Linux|Any CPU 13 | Release-Linux|Any CPU = Release-Linux|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Debug-Windows|Any CPU.ActiveCfg = Debug-Windows|Any CPU 17 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Debug-Windows|Any CPU.Build.0 = Debug-Windows|Any CPU 18 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Release-Windows|Any CPU.ActiveCfg = Release-Windows|Any CPU 19 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Release-Windows|Any CPU.Build.0 = Release-Windows|Any CPU 20 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Debug-Linux|Any CPU.ActiveCfg = Debug-Linux|Any CPU 21 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Debug-Linux|Any CPU.Build.0 = Debug-Linux|Any CPU 22 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Release-Linux|Any CPU.ActiveCfg = Release-Linux|Any CPU 23 | {1E49C5CD-BB47-4FFD-9AF1-DDBE234CBFB1}.Release-Linux|Any CPU.Build.0 = Release-Linux|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {6BEA8DBA-706C-4E3C-87E2-67043D9F1D6A} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /xalia.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32407.343 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xalia", "xalia\xalia.csproj", "{35CC3931-620A-4DF1-9EAE-F9B6E1E72AC1}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F1200842-1F77-4A05-8052-95CE01C976EE}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug-NetStandard|Any CPU = Debug-NetStandard|Any CPU 16 | Release-NetStandard|Any CPU = Release-NetStandard|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {35CC3931-620A-4DF1-9EAE-F9B6E1E72AC1}.Debug-NetStandard|Any CPU.ActiveCfg = Debug-NetStandard|Any CPU 20 | {35CC3931-620A-4DF1-9EAE-F9B6E1E72AC1}.Debug-NetStandard|Any CPU.Build.0 = Debug-NetStandard|Any CPU 21 | {35CC3931-620A-4DF1-9EAE-F9B6E1E72AC1}.Release-NetStandard|Any CPU.ActiveCfg = Release-NetStandard|Any CPU 22 | {35CC3931-620A-4DF1-9EAE-F9B6E1E72AC1}.Release-NetStandard|Any CPU.Build.0 = Release-NetStandard|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {F5F12719-3E79-4DCA-8F22-EB7D6DD7C105} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /xalia/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /xalia/AtSpi2/ActionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Tmds.DBus.Protocol; 5 | using Xalia.Gudl; 6 | using Xalia.UiDom; 7 | using static Xalia.AtSpi2.DBusUtils; 8 | 9 | namespace Xalia.AtSpi2 10 | { 11 | internal class ActionProvider : UiDomProviderBase 12 | { 13 | public ActionProvider(AccessibleProvider accessible) 14 | { 15 | Accessible = accessible; 16 | } 17 | 18 | public AccessibleProvider Accessible { get; } 19 | 20 | public AtSpiConnection Connection => Accessible.Connection; 21 | public string Peer => Accessible.Peer; 22 | public string Path => Accessible.Path; 23 | public UiDomElement Element => Accessible.Element; 24 | 25 | // Sync with AccessibleProvider.other_interface_properties 26 | private static readonly Dictionary property_aliases = new Dictionary 27 | { 28 | { "action", "spi_action" }, 29 | { "do_default_action", "spi_do_default_action" }, 30 | }; 31 | 32 | public string[] Actions { get; private set; } 33 | private bool fetching_actions; 34 | 35 | public override void DumpProperties(UiDomElement element) 36 | { 37 | if (!(Actions is null)) 38 | Utils.DebugWriteLine($" spi_action: [{string.Join(",", Actions)}]"); 39 | } 40 | 41 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 42 | { 43 | switch (identifier) 44 | { 45 | case "spi_action": 46 | depends_on.Add((element, new IdentifierExpression("spi_action"))); 47 | if (!(Actions is null)) 48 | return new AtSpiActionList(this); 49 | return UiDomUndefined.Instance; 50 | case "spi_do_default_action": 51 | return new UiDomRoutineAsync(element, "spi_do_default_action", DoDefaultAction); 52 | } 53 | return UiDomUndefined.Instance; 54 | } 55 | 56 | private static async Task DoDefaultAction(UiDomRoutineAsync obj) 57 | { 58 | await obj.Element.ProviderByType().DoAction(0); 59 | } 60 | 61 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 62 | { 63 | if (property_aliases.TryGetValue(identifier, out var aliased)) 64 | { 65 | return element.EvaluateIdentifier(aliased, element.Root, depends_on); 66 | } 67 | return UiDomUndefined.Instance; 68 | } 69 | 70 | public override bool WatchProperty(UiDomElement element, GudlExpression expression) 71 | { 72 | if (expression is IdentifierExpression id) 73 | { 74 | switch (id.Name) 75 | { 76 | case "spi_action": 77 | if (!fetching_actions) 78 | { 79 | fetching_actions = true; 80 | Utils.RunTask(FetchActions()); 81 | } 82 | return true; 83 | } 84 | } 85 | return false; 86 | } 87 | 88 | private async Task FetchActions() 89 | { 90 | string[] result; 91 | try 92 | { 93 | int count = await GetPropertyInt(Connection.Connection, Peer, Path, IFACE_ACTION, "NActions"); 94 | result = new string[count]; 95 | for (int i = 0; i < count; i++) 96 | { 97 | result[i] = await CallMethod(Connection.Connection, Peer, Path, IFACE_ACTION, 98 | "GetName", i, ReadMessageString); 99 | } 100 | } 101 | catch (DBusException e) 102 | { 103 | if (!AtSpiConnection.IsExpectedException(e, "org.freedesktop.DBus.Error.Failed")) 104 | throw; 105 | return; 106 | } 107 | Actions = result; 108 | if (Element.MatchesDebugCondition()) 109 | Utils.DebugWriteLine($"{Element}.spi_action: ({string.Join(",", Actions)})"); 110 | Element.PropertyChanged("spi_action"); 111 | } 112 | 113 | public async Task DoAction(int index) 114 | { 115 | bool success; 116 | try 117 | { 118 | success = await CallMethod(Connection.Connection, Peer, Path, 119 | IFACE_ACTION, "DoAction", index, ReadMessageBoolean); 120 | } 121 | catch (DBusException e) 122 | { 123 | if (!AtSpiConnection.IsExpectedException(e)) 124 | throw; 125 | return; 126 | } 127 | if (!success) 128 | { 129 | Utils.DebugWriteLine($"WARNING: {Element}.spi_action({index}) failed"); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /xalia/AtSpi2/ApplicationProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Tmds.DBus.Protocol; 4 | using Xalia.Gudl; 5 | using Xalia.UiDom; 6 | using static Xalia.AtSpi2.DBusUtils; 7 | 8 | namespace Xalia.AtSpi2 9 | { 10 | internal class ApplicationProvider : UiDomProviderBase 11 | { 12 | public ApplicationProvider(AccessibleProvider accessible) 13 | { 14 | Accessible = accessible; 15 | } 16 | 17 | public AccessibleProvider Accessible { get; } 18 | 19 | public AtSpiConnection Connection => Accessible.Connection; 20 | public string Peer => Accessible.Peer; 21 | public string Path => Accessible.Path; 22 | public UiDomElement Element => Accessible.Element; 23 | 24 | public bool ToolkitNameKnown { get; private set; } 25 | public string ToolkitName { get; private set; } 26 | private bool fetching_toolkit_name; 27 | 28 | // Sync with AccessibleProvider.other_interface_properties 29 | private static readonly Dictionary property_aliases = new Dictionary 30 | { 31 | { "toolkit_name", "spi_toolkit_name" }, 32 | }; 33 | 34 | public override void DumpProperties(UiDomElement element) 35 | { 36 | if (ToolkitNameKnown) 37 | Utils.DebugWriteLine($" spi_toolkit_name: \"{ToolkitName}\""); 38 | } 39 | 40 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 41 | { 42 | switch (identifier) 43 | { 44 | case "spi_toolkit_name": 45 | depends_on.Add((element, new IdentifierExpression(identifier))); 46 | if (ToolkitNameKnown) 47 | return new UiDomString(ToolkitName); 48 | return UiDomUndefined.Instance; 49 | } 50 | return UiDomUndefined.Instance; 51 | } 52 | 53 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 54 | { 55 | if (property_aliases.TryGetValue(identifier, out var aliased)) 56 | { 57 | return element.EvaluateIdentifier(aliased, element.Root, depends_on); 58 | } 59 | return UiDomUndefined.Instance; 60 | } 61 | 62 | private async Task FetchToolkitName() 63 | { 64 | VariantValue result; 65 | try 66 | { 67 | result = await GetProperty(Connection.Connection, Peer, Path, 68 | IFACE_APPLICATION, "ToolkitName"); 69 | } 70 | catch (DBusException e) 71 | { 72 | if (!AtSpiConnection.IsExpectedException(e)) 73 | throw; 74 | return; 75 | } 76 | 77 | if (result.Type == VariantValueType.String) 78 | { 79 | string st = result.GetString(); 80 | ToolkitNameKnown = true; 81 | ToolkitName = st; 82 | Element.PropertyChanged("spi_toolkit_name", ToolkitName); 83 | return; 84 | } 85 | 86 | if (result.Type == VariantValueType.Invalid) 87 | Utils.DebugWriteLine($"WARNING: {Element} returned null for ToolkitName"); 88 | else 89 | Utils.DebugWriteLine($"WARNING: {Element} returned {result} for ToolkitName"); 90 | } 91 | 92 | public override bool WatchProperty(UiDomElement element, GudlExpression expression) 93 | { 94 | if (expression is IdentifierExpression id) 95 | { 96 | switch (id.Name) 97 | { 98 | case "spi_toolkit_name": 99 | if (!fetching_toolkit_name) 100 | { 101 | fetching_toolkit_name = true; 102 | Utils.RunTask(FetchToolkitName()); 103 | } 104 | break; 105 | } 106 | } 107 | return false; 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /xalia/AtSpi2/AtSpiActionList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Threading.Tasks; 5 | using Xalia.Gudl; 6 | using Xalia.UiDom; 7 | 8 | namespace Xalia.AtSpi2 9 | { 10 | internal class AtSpiActionList : UiDomValue 11 | { 12 | public AtSpiActionList(ActionProvider provider) 13 | { 14 | Provider = provider; 15 | } 16 | 17 | public ActionProvider Provider { get; } 18 | 19 | public UiDomElement Element => Provider.Element; 20 | 21 | public override bool Equals(object obj) 22 | { 23 | if (ReferenceEquals(this, obj)) 24 | return true; 25 | if (obj is AtSpiActionList l) 26 | { 27 | return l.Element.Equals(Element); 28 | } 29 | return false; 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | return typeof(AtSpiActionList).GetHashCode() ^ Element.GetHashCode(); 35 | } 36 | 37 | public override string ToString() 38 | { 39 | return $"{Element}.spi_action [{string.Join(",",Provider.Actions)}]"; 40 | } 41 | 42 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 43 | { 44 | for (int i=0; i < Provider.Actions.Length; i++) 45 | { 46 | if (Provider.Actions[i] == id) 47 | { 48 | return new UiDomRoutineAsync( 49 | Element, $"spi_action.{id}", HandleAction); 50 | } 51 | } 52 | return base.EvaluateIdentifierCore(id, root, depends_on); 53 | } 54 | 55 | protected override UiDomValue EvaluateApply(UiDomValue context, GudlExpression[] arglist, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 56 | { 57 | if (arglist.Length != 1) 58 | return UiDomUndefined.Instance; 59 | var expr = arglist[0]; 60 | UiDomValue right = context.Evaluate(expr, root, depends_on); 61 | if (right is UiDomString st) 62 | { 63 | return EvaluateIdentifier(st.Value, root, depends_on); 64 | } 65 | if (right.TryToInt(out int i)) 66 | { 67 | return new UiDomRoutineAsync(Element, "spi_action", new UiDomValue[] { right }, HandleApply); 68 | } 69 | return UiDomUndefined.Instance; 70 | } 71 | 72 | private static Task HandleApply(UiDomRoutineAsync obj) 73 | { 74 | var element = obj.Element; 75 | obj.Arglist[0].TryToInt(out int index); /* already checked in EvaluateApply */ 76 | return element.ProviderByType().DoAction(index); 77 | } 78 | 79 | private static Task HandleAction(UiDomRoutineAsync obj) 80 | { 81 | string id = obj.Name.Substring(11); 82 | var element = obj.Element; 83 | var provider = element.ProviderByType(); 84 | for (int i=0; i attributes) : base() 12 | { 13 | Attributes = attributes; 14 | } 15 | 16 | public Dictionary Attributes { get; } 17 | 18 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 19 | { 20 | if (Attributes.TryGetValue(id, out var result)) 21 | { 22 | return new UiDomString(result); 23 | } 24 | if (id.Contains("_") && Attributes.TryGetValue(id.Replace("_","-"), out result)) 25 | { 26 | return new UiDomString(result); 27 | } 28 | return base.EvaluateIdentifierCore(id, root, depends_on); 29 | } 30 | 31 | public override bool Equals(object obj) 32 | { 33 | if (ReferenceEquals(this, obj)) 34 | return true; 35 | if (obj is AtSpiAttributes a) 36 | { 37 | return Utils.DictionariesEqual(a.Attributes, Attributes); 38 | } 39 | return false; 40 | } 41 | 42 | public override int GetHashCode() 43 | { 44 | int result = Attributes.Count ^ typeof(AtSpiAttributes).GetHashCode(); 45 | foreach (var kvp in Attributes) 46 | { 47 | result ^= kvp.Key.GetHashCode(); 48 | result ^= kvp.Value.GetHashCode(); 49 | } 50 | return result; 51 | } 52 | 53 | public override string ToString() 54 | { 55 | return $"spi_attributes [{Attributes.Count.ToString(CultureInfo.InvariantCulture)}]"; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /xalia/AtSpi2/AtSpiState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using Xalia.Gudl; 7 | using Xalia.UiDom; 8 | 9 | namespace Xalia.AtSpi2 10 | { 11 | internal class AtSpiState : UiDomValue 12 | { 13 | public AtSpiState(uint[] flags) 14 | { 15 | Flags = flags; 16 | } 17 | 18 | public uint[] Flags { get; } 19 | 20 | public override bool Equals(object obj) 21 | { 22 | if (ReferenceEquals(this, obj)) 23 | return true; 24 | if (obj is AtSpiState st) 25 | { 26 | if (Flags.Length != st.Flags.Length) 27 | return false; 28 | for (int i=0; i IsStateSet(Flags, state); 79 | 80 | public bool IsStateSet(string state) => IsStateSet(Flags, state); 81 | 82 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 83 | { 84 | if (AccessibleProvider.name_to_state.TryGetValue(id, out var state)) 85 | return UiDomBoolean.FromBool(IsStateSet(state)); 86 | return base.EvaluateIdentifierCore(id, root, depends_on); 87 | } 88 | 89 | public override string ToString() 90 | { 91 | StringBuilder sb = new StringBuilder(); 92 | bool first_token = true; 93 | sb.Append("'); 114 | return sb.ToString(); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /xalia/AtSpi2/AtSpiSupported.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using Xalia.Gudl; 7 | using Xalia.UiDom; 8 | 9 | namespace Xalia.AtSpi2 10 | { 11 | internal class AtSpiSupported : UiDomValue 12 | { 13 | public AtSpiSupported(string[] interfaces) 14 | { 15 | Interfaces = interfaces; 16 | } 17 | 18 | public string[] Interfaces { get; } 19 | 20 | public override string ToString() 21 | { 22 | var result = new StringBuilder(); 23 | result.Append($"spi_supported["); 24 | bool delimiter = false; 25 | foreach (string iface in Interfaces) 26 | { 27 | if (delimiter) 28 | result.Append("|"); 29 | String name; 30 | if (iface.StartsWith("org.a11y.atspi.")) 31 | name = ToSnakeCase(iface.Substring(15)); 32 | else 33 | name = iface; 34 | result.Append(name); 35 | delimiter = true; 36 | } 37 | result.Append("]"); 38 | return result.ToString(); 39 | } 40 | 41 | static string ToCamelCase(string input) 42 | { 43 | var sb = new StringBuilder(); 44 | 45 | bool capitalize = true; 46 | 47 | foreach (char c in input) 48 | { 49 | if (c == '_') 50 | { 51 | capitalize = true; 52 | continue; 53 | } 54 | 55 | if (capitalize) 56 | { 57 | sb.Append(char.ToUpper(c)); 58 | capitalize = false; 59 | continue; 60 | } 61 | 62 | sb.Append(c); 63 | } 64 | 65 | return sb.ToString(); 66 | } 67 | 68 | static string ToSnakeCase(string input) 69 | { 70 | var sb = new StringBuilder(); 71 | 72 | bool at_start = true; 73 | 74 | foreach (char c in input) 75 | { 76 | if (at_start) 77 | { 78 | sb.Append(char.ToLower(c)); 79 | at_start = false; 80 | continue; 81 | } 82 | 83 | if (char.IsUpper(c)) 84 | { 85 | sb.Append("_"); 86 | sb.Append(char.ToLower(c)); 87 | continue; 88 | } 89 | 90 | sb.Append(c); 91 | at_start = false; 92 | } 93 | 94 | return sb.ToString(); 95 | } 96 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 97 | { 98 | string iface_name; 99 | 100 | if (id.Contains(".")) 101 | { 102 | iface_name = id; 103 | } 104 | else 105 | { 106 | iface_name = "org.a11y.atspi." + ToCamelCase(id); 107 | } 108 | 109 | return UiDomBoolean.FromBool(Interfaces.Contains(iface_name)); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /xalia/AtSpi2/SelectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Tmds.DBus.Protocol; 4 | using Xalia.Gudl; 5 | using Xalia.UiDom; 6 | using static Xalia.AtSpi2.DBusUtils; 7 | 8 | namespace Xalia.AtSpi2 9 | { 10 | internal class SelectionProvider : UiDomProviderBase 11 | { 12 | public SelectionProvider(AccessibleProvider accessibleProvider) 13 | { 14 | AccessibleProvider = accessibleProvider; 15 | } 16 | 17 | public AccessibleProvider AccessibleProvider { get; } 18 | public AtSpiConnection Connection => AccessibleProvider.Connection; 19 | public string Peer => AccessibleProvider.Peer; 20 | public string Path => AccessibleProvider.Path; 21 | 22 | // Sync with AccessibleProvider.other_interface_properties 23 | private static readonly Dictionary property_aliases = new Dictionary 24 | { 25 | { "clear_selection", "spi_clear_selection" }, 26 | }; 27 | 28 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 29 | { 30 | switch (identifier) 31 | { 32 | case "spi_clear_selection": 33 | return new UiDomRoutineAsync(element, "spi_clear_selection", ClearSelectionAsync); 34 | } 35 | return base.EvaluateIdentifier(element, identifier, depends_on); 36 | } 37 | 38 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 39 | { 40 | if (property_aliases.TryGetValue(identifier, out var aliased)) 41 | { 42 | return element.EvaluateIdentifier(aliased, element.Root, depends_on); 43 | } 44 | return base.EvaluateIdentifierLate(element, identifier, depends_on); 45 | } 46 | 47 | private static async Task ClearSelectionAsync(UiDomRoutineAsync obj) 48 | { 49 | var provider = obj.Element.ProviderByType(); 50 | try 51 | { 52 | var result = await CallMethod(provider.Connection.Connection, provider.Peer, provider.Path, IFACE_SELECTION, 53 | "ClearSelection", ReadMessageBoolean); 54 | 55 | if (!result) 56 | { 57 | Utils.DebugWriteLine($"WARNING: ClearSelection failed for {obj.Element}"); 58 | } 59 | } 60 | catch (DBusException e) 61 | { 62 | if (!AtSpiConnection.IsExpectedException(e)) 63 | throw; 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /xalia/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods")] 9 | -------------------------------------------------------------------------------- /xalia/Gudl/ApplyExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Xalia.Gudl 4 | { 5 | internal class ApplyExpression : GudlExpression 6 | { 7 | public ApplyExpression(GudlExpression left, GudlExpression[] arglist) 8 | { 9 | Left = left; 10 | Arglist = arglist; 11 | } 12 | 13 | public GudlExpression Left { get; } 14 | public GudlExpression[] Arglist { get; } 15 | 16 | public override bool Equals(object obj) 17 | { 18 | if (ReferenceEquals(this, obj)) 19 | return true; 20 | if (obj is ApplyExpression apply) 21 | { 22 | if (Arglist.Length != apply.Arglist.Length) 23 | return false; 24 | if (!Left.Equals(apply.Left)) 25 | return false; 26 | for (int i = 0; i < Arglist.Length; i++) 27 | { 28 | if (!Arglist[i].Equals(apply.Arglist[i])) 29 | return false; 30 | } 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | public override int GetHashCode() 37 | { 38 | int result = typeof(ApplyExpression).GetHashCode() ^ Left.GetHashCode(); 39 | foreach (var arg in Arglist) 40 | { 41 | result = (result, arg).GetHashCode(); 42 | } 43 | return result; 44 | } 45 | 46 | internal override string ToString(out GudlPrecedence precedence) 47 | { 48 | var result = new StringBuilder(); 49 | var left_str = Left.ToString(out var left_precedence); 50 | if (left_precedence < GudlPrecedence.Apply) 51 | result.Append("("); 52 | result.Append(left_str); 53 | if (left_precedence < GudlPrecedence.Apply) 54 | result.Append(")"); 55 | result.Append("("); 56 | for (int i=0; i < Arglist.Length; i++) 57 | { 58 | result.Append(Arglist[i]); 59 | if (i < Arglist.Length - 1) 60 | result.Append(", "); 61 | } 62 | result.Append(")"); 63 | precedence = GudlPrecedence.Apply; 64 | return result.ToString(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /xalia/Gudl/BinaryExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.Gudl 2 | { 3 | public class BinaryExpression : GudlExpression 4 | { 5 | public BinaryExpression(GudlExpression left, GudlExpression right, GudlToken op) 6 | { 7 | Left = left; 8 | Right = right; 9 | Kind = op; 10 | } 11 | 12 | public GudlExpression Left { get; } 13 | public GudlExpression Right { get; } 14 | public GudlToken Kind { get; } 15 | public override bool Equals(object obj) 16 | { 17 | if (ReferenceEquals(this, obj)) 18 | return true; 19 | if (obj is BinaryExpression bin) 20 | return Kind == bin.Kind && Left.Equals(bin.Left) && Right.Equals(bin.Right); 21 | return false; 22 | } 23 | 24 | public override int GetHashCode() 25 | { 26 | return (Left, Right, Kind).GetHashCode() ^ typeof(BinaryExpression).GetHashCode(); 27 | } 28 | 29 | internal override string ToString(out GudlPrecedence precedence) 30 | { 31 | string opname; 32 | switch (Kind) 33 | { 34 | case GudlToken.And: 35 | opname = " and "; 36 | precedence = GudlPrecedence.And; 37 | break; 38 | case GudlToken.Dot: 39 | opname = "."; 40 | precedence = GudlPrecedence.Dot; 41 | break; 42 | case GudlToken.Equal: 43 | opname = " == "; 44 | precedence = GudlPrecedence.Inequality; 45 | break; 46 | case GudlToken.NotEqual: 47 | opname = " != "; 48 | precedence = GudlPrecedence.Inequality; 49 | break; 50 | case GudlToken.Lt: 51 | opname = " < "; 52 | precedence = GudlPrecedence.Inequality; 53 | break; 54 | case GudlToken.Gt: 55 | opname = " > "; 56 | precedence = GudlPrecedence.Inequality; 57 | break; 58 | case GudlToken.Lte: 59 | opname = " <= "; 60 | precedence = GudlPrecedence.Inequality; 61 | break; 62 | case GudlToken.Gte: 63 | opname = " >= "; 64 | precedence = GudlPrecedence.Inequality; 65 | break; 66 | case GudlToken.Or: 67 | opname = " or "; 68 | precedence = GudlPrecedence.Or; 69 | break; 70 | case GudlToken.Mult: 71 | opname = " * "; 72 | precedence = GudlPrecedence.Product; 73 | break; 74 | case GudlToken.IDiv: 75 | opname = " ~/ "; 76 | precedence = GudlPrecedence.Product; 77 | break; 78 | case GudlToken.Modulo: 79 | opname = " ~ "; 80 | precedence = GudlPrecedence.Product; 81 | break; 82 | case GudlToken.Div: 83 | opname = " / "; 84 | precedence = GudlPrecedence.Product; 85 | break; 86 | case GudlToken.Plus: 87 | opname = " + "; 88 | precedence = GudlPrecedence.Sum; 89 | break; 90 | case GudlToken.Minus: 91 | opname = " - "; 92 | precedence = GudlPrecedence.Sum; 93 | break; 94 | default: 95 | precedence = GudlPrecedence.Atom; 96 | return base.ToString(); 97 | } 98 | var left_str = Left.ToString(out GudlPrecedence left_precedence); 99 | if (left_precedence < precedence) 100 | left_str = $"({left_str})"; 101 | var right_str = Right.ToString(out GudlPrecedence right_precedence); 102 | if (right_precedence <= precedence) 103 | right_str = $"({right_str})"; 104 | return $"{left_str}{opname}{right_str}"; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /xalia/Gudl/DoubleExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Xalia.Gudl 4 | { 5 | internal class DoubleExpression : GudlExpression 6 | { 7 | public DoubleExpression(double value) 8 | { 9 | Value = value; 10 | } 11 | 12 | public double Value { get; } 13 | 14 | public override bool Equals(object obj) 15 | { 16 | if (ReferenceEquals(this, obj)) 17 | return true; 18 | if (obj is DoubleExpression d) 19 | { 20 | return Value == d.Value; 21 | } 22 | return false; 23 | } 24 | 25 | public override int GetHashCode() 26 | { 27 | return Value.GetHashCode() ^ typeof(DoubleExpression).GetHashCode(); 28 | } 29 | 30 | internal override string ToString(out GudlPrecedence precedence) 31 | { 32 | precedence = GudlPrecedence.Atom; 33 | return Value.ToString(CultureInfo.InvariantCulture); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /xalia/Gudl/GudlDeclaration.cs: -------------------------------------------------------------------------------- 1 | using Superpower.Model; 2 | using System; 3 | 4 | namespace Xalia.Gudl 5 | { 6 | public class GudlDeclaration : GudlStatement 7 | { 8 | public GudlDeclaration(GudlExpression property, GudlExpression value, Superpower.Model.Position position) 9 | { 10 | if (property is IdentifierExpression id) 11 | Property = id.Name; 12 | else if (property is StringExpression st) 13 | Property = st.Value; 14 | else 15 | throw new ArgumentException("property must be an IdentifierExpression or StringExpression"); 16 | Value = value; 17 | Position = position; 18 | } 19 | 20 | public string Property; 21 | public GudlExpression Value; 22 | public Position Position; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /xalia/Gudl/GudlExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.Gudl 2 | { 3 | public abstract class GudlExpression 4 | { 5 | internal abstract string ToString(out GudlPrecedence precedence); 6 | 7 | public override string ToString() 8 | { 9 | return ToString(out GudlPrecedence _precedence); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /xalia/Gudl/GudlPrecedence.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia 2 | { 3 | internal enum GudlPrecedence 4 | { 5 | Or, 6 | And, 7 | Not, 8 | Inequality, 9 | Sum, 10 | Product, 11 | Sign, 12 | Apply, 13 | Dot, 14 | Atom, 15 | } 16 | } -------------------------------------------------------------------------------- /xalia/Gudl/GudlSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Xalia.Gudl 5 | { 6 | public class GudlSelector : GudlStatement 7 | { 8 | static GudlSelector() 9 | { 10 | relationship_conditions = new Dictionary(); 11 | relationship_conditions["parent"] = "child_matches"; 12 | relationship_conditions["ancestor"] = "descendent_matches"; 13 | relationship_conditions["child"] = "parent_matches"; 14 | relationship_conditions["descendent"] = "ancestor_matches"; 15 | relationship_conditions["previous_sibling"] = "next_sibling_matches"; 16 | relationship_conditions["next_sibling"] = "previous_sibling_matches"; 17 | } 18 | 19 | public GudlSelector(GudlExpression kind, GudlExpression condition, GudlStatement[] statements) 20 | { 21 | if (kind is IdentifierExpression id) 22 | Kind = id.Name; 23 | else if (kind is StringExpression st) 24 | Kind = st.Value; 25 | else 26 | throw new ArgumentException("kind must be an IdentifierExpression or StringExpression"); 27 | Condition = condition; 28 | Statements = statements; 29 | } 30 | 31 | static Dictionary relationship_conditions; 32 | 33 | public string Kind; 34 | public GudlExpression Condition; 35 | public GudlStatement[] Statements; 36 | 37 | private GudlExpression And(GudlExpression left, GudlExpression right) 38 | { 39 | if (left is null) 40 | return right; 41 | else if (right is null) 42 | return left; 43 | else 44 | return new BinaryExpression(left, right, GudlToken.And); 45 | } 46 | 47 | private void FlattenRecursive(GudlExpression parent_condition, List<(GudlExpression, GudlDeclaration[])> items) 48 | { 49 | GudlExpression condition = Condition; 50 | if (!(Kind is null) && Kind != "if") 51 | { 52 | if (relationship_conditions.ContainsKey(Kind)) 53 | { 54 | condition = And( 55 | new ApplyExpression(new IdentifierExpression(relationship_conditions[Kind]), 56 | new GudlExpression[] { parent_condition ?? new IdentifierExpression("true") }), 57 | condition); 58 | } 59 | else if (Kind == "root") 60 | { 61 | condition = And( 62 | new IdentifierExpression("is_root"), 63 | condition); 64 | if (parent_condition != null) 65 | { 66 | condition = And( 67 | condition, 68 | new ApplyExpression( 69 | new IdentifierExpression("this_or_descendent_matches"), 70 | new GudlExpression[] { parent_condition })); 71 | } 72 | } 73 | else 74 | { 75 | condition = And( 76 | And(new IdentifierExpression(Kind), parent_condition), 77 | condition); 78 | } 79 | } 80 | else 81 | { 82 | condition = And(parent_condition, condition); 83 | } 84 | 85 | List declarations = new List(); 86 | 87 | foreach (var statement in Statements) 88 | { 89 | if (statement is GudlDeclaration declaration) 90 | { 91 | declarations.Add(declaration); 92 | } 93 | else 94 | { 95 | if (declarations.Count != 0) 96 | items.Add((condition, declarations.ToArray())); 97 | declarations.Clear(); 98 | GudlSelector selector = (GudlSelector)statement; 99 | selector.FlattenRecursive(condition, items); 100 | } 101 | } 102 | 103 | if (declarations.Count != 0) 104 | items.Add((condition, declarations.ToArray())); 105 | } 106 | 107 | public List<(GudlExpression, GudlDeclaration[])> Flatten() 108 | { 109 | var result = new List<(GudlExpression, GudlDeclaration[])>(); 110 | FlattenRecursive(null, result); 111 | return result; 112 | } 113 | 114 | public static List<(GudlExpression, GudlDeclaration[])> Flatten(GudlStatement[] statements) 115 | { 116 | var items = new List<(GudlExpression, GudlDeclaration[])>(); 117 | 118 | List declarations = new List(); 119 | 120 | foreach (var statement in statements) 121 | { 122 | if (statement is GudlDeclaration declaration) 123 | { 124 | declarations.Add(declaration); 125 | } 126 | else 127 | { 128 | if (declarations.Count != 0) 129 | items.Add((null, declarations.ToArray())); 130 | declarations.Clear(); 131 | GudlSelector selector = (GudlSelector)statement; 132 | selector.FlattenRecursive(null, items); 133 | } 134 | } 135 | 136 | if (declarations.Count != 0) 137 | items.Add((null, declarations.ToArray())); 138 | 139 | return items; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /xalia/Gudl/GudlStatement.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.Gudl 2 | { 3 | public abstract class GudlStatement 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /xalia/Gudl/GudlToken.cs: -------------------------------------------------------------------------------- 1 | using Superpower.Display; 2 | using Superpower.Parsers; 3 | 4 | namespace Xalia.Gudl 5 | { 6 | public enum GudlToken 7 | { 8 | [Token(Example = "(")] 9 | LParen, 10 | 11 | [Token(Example = ")")] 12 | RParen, 13 | 14 | [Token(Example = ".")] 15 | Dot, 16 | 17 | [Token(Example = "{")] 18 | LBrace, 19 | 20 | [Token(Example = "}")] 21 | RBrace, 22 | 23 | [Token(Example = ":")] 24 | Colon, 25 | 26 | [Token(Example = ";")] 27 | Semicolon, 28 | 29 | [Token(Example = "==")] 30 | Equal, 31 | 32 | [Token(Example = "!=")] 33 | NotEqual, 34 | 35 | [Token(Example = "not")] 36 | Not, 37 | 38 | [Token(Example = "and")] 39 | And, 40 | 41 | [Token(Example = "or")] 42 | Or, 43 | 44 | [Token(Example = "+")] 45 | Plus, 46 | 47 | [Token(Example = "-")] 48 | Minus, 49 | 50 | [Token(Example = "*")] 51 | Mult, 52 | 53 | [Token(Example = "~/")] 54 | IDiv, 55 | 56 | [Token(Example = "%")] 57 | Modulo, 58 | 59 | [Token(Example = "<")] 60 | Lt, 61 | 62 | [Token(Example = ">")] 63 | Gt, 64 | 65 | [Token(Example = "<=")] 66 | Lte, 67 | 68 | [Token(Example = ">=")] 69 | Gte, 70 | 71 | Identifier, 72 | 73 | String, 74 | 75 | DecInteger, 76 | HexInteger, 77 | 78 | [Token(Example = ",")] 79 | Comma, 80 | Double, 81 | 82 | [Token(Example = "/")] 83 | Div, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /xalia/Gudl/GudlTokenizer.cs: -------------------------------------------------------------------------------- 1 | using Superpower; 2 | using Superpower.Model; 3 | using Superpower.Parsers; 4 | using Superpower.Tokenizers; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Numerics; 8 | 9 | namespace Xalia.Gudl 10 | { 11 | static class GudlTokenizer 12 | { 13 | static char HexToChar(char ch) 14 | { 15 | if (ch >= '0' && ch <= '9') 16 | return (char)(ch - '0'); 17 | if (ch >= 'a' && ch <= 'f') 18 | return (char)(ch - 'a' + 10); 19 | //else (ch >= 'A' && ch <= 'F') 20 | return (char)(ch - 'A' + 10); 21 | } 22 | 23 | public static TextParser HexChar(int maxdigits) 24 | { 25 | var singlechar = Character.HexDigit.Select(HexToChar); 26 | 27 | var result = singlechar; 28 | 29 | for (int i = 1; i < maxdigits; i++) 30 | { 31 | result = result.Then(leading => 32 | singlechar.Select(end => (char)(leading * 16 + end)).OptionalOrDefault(leading)); 33 | } 34 | 35 | return result; 36 | } 37 | 38 | // QuotedString.CStyle doesn't seem to allow \\, not sure why 39 | public static TextParser GudlString = 40 | from open in Character.EqualTo('"') 41 | from content in Character.ExceptIn('\\', '"', '\r', '\n') 42 | .Or(Span.EqualTo("\\u").Try().IgnoreThen(HexChar(4))) 43 | .Or(Span.EqualTo("\\x").Try().IgnoreThen(HexChar(2))) 44 | .Or(Span.EqualTo("\\0").Try().Value('\0')) 45 | .Or(Span.EqualTo("\\a").Try().Value('\a')) 46 | .Or(Span.EqualTo("\\b").Try().Value('\b')) 47 | .Or(Span.EqualTo("\\f").Try().Value('\f')) 48 | .Or(Span.EqualTo("\\n").Try().Value('\n')) 49 | .Or(Span.EqualTo("\\r").Try().Value('\r')) 50 | .Or(Span.EqualTo("\\t").Try().Value('\t')) 51 | .Or(Span.EqualTo("\\v").Try().Value('\v')) 52 | .Or(Character.EqualTo('\\').IgnoreThen(Character.ExceptIn('\r', '\n'))).Many() 53 | from close in Character.EqualTo('"') 54 | select new string(content); 55 | 56 | public static TextParser GudlDouble = 57 | from whole in Numerics.Integer 58 | from dot in Character.EqualTo('.') 59 | from dec in Numerics.Integer.OptionalOrDefault(TextSpan.Empty) 60 | select double.Parse($"{whole}.{dec}", CultureInfo.InvariantCulture); 61 | 62 | public static TextParser GudlHexConstant = 63 | from prefix in Span.EqualTo("0x") 64 | from digits in Numerics.HexDigits 65 | select BigInteger.Parse(digits.ToStringValue(), NumberStyles.HexNumber, CultureInfo.InvariantCulture); 66 | 67 | public static Tokenizer Instance = 68 | new TokenizerBuilder() 69 | .Ignore(Span.WhiteSpace) 70 | .Ignore(Comment.CStyle) 71 | .Ignore(Comment.CPlusPlusStyle) 72 | .Match(Character.EqualTo('('), GudlToken.LParen) 73 | .Match(Character.EqualTo(')'), GudlToken.RParen) 74 | .Match(Character.EqualTo('.'), GudlToken.Dot) 75 | .Match(Character.EqualTo('{'), GudlToken.LBrace) 76 | .Match(Character.EqualTo('}'), GudlToken.RBrace) 77 | .Match(Character.EqualTo(':'), GudlToken.Colon) 78 | .Match(Character.EqualTo(';'), GudlToken.Semicolon) 79 | .Match(Character.EqualTo('+'), GudlToken.Plus) 80 | .Match(Character.EqualTo('-'), GudlToken.Minus) 81 | .Match(Character.EqualTo('*'), GudlToken.Mult) 82 | .Match(Character.EqualTo(','), GudlToken.Comma) 83 | .Match(Character.EqualTo('/'), GudlToken.Div) 84 | .Match(Span.EqualTo("~/"), GudlToken.IDiv) 85 | .Match(Character.EqualTo('%'), GudlToken.Modulo) 86 | .Match(Span.EqualTo("=="), GudlToken.Equal) 87 | .Match(Character.EqualTo('='), GudlToken.Equal) 88 | .Match(Span.EqualTo("!="), GudlToken.NotEqual) 89 | .Match(Span.EqualTo("<="), GudlToken.Lte) 90 | .Match(Span.EqualTo(">="), GudlToken.Gte) 91 | .Match(Character.EqualTo('<'), GudlToken.Lt) 92 | .Match(Character.EqualTo('>'), GudlToken.Gt) 93 | .Match(Span.EqualTo("not"), GudlToken.Not, requireDelimiters: true) 94 | .Match(Span.EqualTo("and"), GudlToken.And, requireDelimiters: true) 95 | .Match(Span.EqualTo("or"), GudlToken.Or, requireDelimiters: true) 96 | .Match(Identifier.CStyle, GudlToken.Identifier, requireDelimiters: true) 97 | .Match(GudlString, GudlToken.String, requireDelimiters: true) 98 | .Match(GudlDouble, GudlToken.Double, requireDelimiters: true) 99 | .Match(Numerics.Integer, GudlToken.DecInteger, requireDelimiters: true) 100 | .Match(GudlHexConstant, GudlToken.HexInteger, requireDelimiters: true) 101 | .Build(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /xalia/Gudl/IdentifierExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.Gudl 2 | { 3 | public class IdentifierExpression : GudlExpression 4 | { 5 | public IdentifierExpression(string name) 6 | { 7 | this.Name = name; 8 | } 9 | 10 | public string Name { get; } 11 | public override bool Equals(object obj) 12 | { 13 | if (ReferenceEquals(this, obj)) 14 | return true; 15 | if (obj is IdentifierExpression id) 16 | return Name == id.Name; 17 | return false; 18 | } 19 | 20 | public override int GetHashCode() 21 | { 22 | return Name.GetHashCode() ^ typeof(IdentifierExpression).GetHashCode(); 23 | } 24 | 25 | internal override string ToString(out GudlPrecedence precedence) 26 | { 27 | precedence = GudlPrecedence.Atom; 28 | return Name.ToString(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /xalia/Gudl/IntegerExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Numerics; 3 | 4 | namespace Xalia.Gudl 5 | { 6 | internal class IntegerExpression : GudlExpression 7 | { 8 | public IntegerExpression(BigInteger value) 9 | { 10 | Value = value; 11 | } 12 | 13 | public IntegerExpression(int value) : this(new BigInteger(value)) { } 14 | 15 | public BigInteger Value { get; } 16 | 17 | public override bool Equals(object obj) 18 | { 19 | if (ReferenceEquals(this, obj)) 20 | return true; 21 | if (obj is IntegerExpression i) 22 | return Value == i.Value; 23 | return false; 24 | } 25 | 26 | public override int GetHashCode() 27 | { 28 | return Value.GetHashCode() ^ typeof(IntegerExpression).GetHashCode(); 29 | } 30 | 31 | internal override string ToString(out GudlPrecedence precedence) 32 | { 33 | precedence = GudlPrecedence.Atom; 34 | return Value.ToString(CultureInfo.InvariantCulture); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /xalia/Gudl/StringExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.Gudl 2 | { 3 | public class StringExpression : GudlExpression 4 | { 5 | public StringExpression(string value) 6 | { 7 | Value = value; 8 | } 9 | 10 | public string Value { get; } 11 | public override bool Equals(object obj) 12 | { 13 | if (ReferenceEquals(this, obj)) 14 | return true; 15 | if (obj is StringExpression st) 16 | return Value == st.Value; 17 | return false; 18 | } 19 | 20 | public override int GetHashCode() 21 | { 22 | return Value.GetHashCode() ^ typeof(StringExpression).GetHashCode(); 23 | } 24 | 25 | internal override string ToString(out GudlPrecedence precedence) 26 | { 27 | // FIXME: add escapes if necessary 28 | precedence = GudlPrecedence.Atom; 29 | return $"\"{Value}\""; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /xalia/Gudl/UnaryExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.Gudl 2 | { 3 | public class UnaryExpression : GudlExpression 4 | { 5 | public UnaryExpression(GudlExpression inner, GudlToken op) 6 | { 7 | Inner = inner; 8 | Kind = op; 9 | } 10 | 11 | public GudlExpression Inner { get; } 12 | public GudlToken Kind { get; } 13 | public override bool Equals(object obj) 14 | { 15 | if (ReferenceEquals(this, obj)) 16 | return true; 17 | if (obj is UnaryExpression u) 18 | return Kind == u.Kind && Inner.Equals(u.Inner); 19 | return false; 20 | } 21 | 22 | public override int GetHashCode() 23 | { 24 | return (Inner, Kind).GetHashCode() ^ typeof(UnaryExpression).GetHashCode(); 25 | } 26 | 27 | internal override string ToString(out GudlPrecedence precedence) 28 | { 29 | string opname; 30 | switch (Kind) 31 | { 32 | case GudlToken.Not: 33 | opname = "not "; 34 | precedence = GudlPrecedence.Not; 35 | break; 36 | case GudlToken.Plus: 37 | opname = "+"; 38 | precedence = GudlPrecedence.Sign; 39 | break; 40 | case GudlToken.Minus: 41 | opname = "-"; 42 | precedence = GudlPrecedence.Sign; 43 | break; 44 | default: 45 | precedence = GudlPrecedence.Atom; 46 | return base.ToString(); 47 | } 48 | var inner_str = Inner.ToString(out var inner_precedence); 49 | if (inner_precedence < precedence) 50 | return $"{opname}({inner_str})"; 51 | return $"{opname}{inner_str}"; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /xalia/Input/InputBackend.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Xalia.Input 4 | { 5 | public abstract class InputBackend 6 | { 7 | protected InputBackend() 8 | { 9 | InputSystem.Instance.RegisterBackend(this); 10 | } 11 | 12 | protected internal abstract bool WatchAction(string name); 13 | protected internal abstract bool UnwatchAction(string name); 14 | 15 | protected void ActionMappingUpdated(string name, InputMapping[] mappings) 16 | { 17 | } 18 | 19 | protected void ActionStateUpdated(string name) 20 | { 21 | InputSystem.Instance.UpdateActionState(name); 22 | } 23 | 24 | protected internal abstract InputState GetActionState(string action); 25 | 26 | protected virtual bool ActivateMode(string name) 27 | { 28 | return false; 29 | } 30 | 31 | protected virtual bool DeactivateMode(string name) 32 | { 33 | return false; 34 | } 35 | 36 | protected void ModeActivated(string name) 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | 41 | protected void ModeDeactivated(string name) 42 | { 43 | throw new NotImplementedException(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /xalia/Input/InputMapping.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.Input 2 | { 3 | public class InputMapping 4 | { 5 | public InputMapping(string name, string pngfilename) 6 | { 7 | HumanReadableName = name; 8 | GlyphPngFilename = pngfilename; 9 | } 10 | 11 | public InputMapping(string name, string pngfilename, string glyphstring, int x, int y, int height) : 12 | this(name, pngfilename) 13 | { 14 | GlyphString = glyphstring; 15 | GlyphStringCenterX = x; 16 | GlyphStringCenterY = y; 17 | GlyphStringHeight = height; 18 | } 19 | 20 | public string GlyphPngFilename { get; } 21 | public string GlyphString { get; } 22 | public int GlyphStringCenterX { get; } 23 | public int GlyphStringCenterY { get; } 24 | public int GlyphStringHeight { get; } 25 | 26 | public string HumanReadableName { get; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /xalia/Input/InputQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Xalia.Input 6 | { 7 | public class InputQueue 8 | { 9 | private Queue states = new Queue(); 10 | private TaskCompletionSource input_ready_task = new TaskCompletionSource(); 11 | private TaskCompletionSource input_exhausted_task = new TaskCompletionSource(); 12 | 13 | public InputQueue() 14 | { 15 | } 16 | 17 | private InputState DequeueInternal() 18 | { 19 | var result = states.Dequeue(); 20 | input_ready_task = new TaskCompletionSource(); 21 | return result; 22 | } 23 | 24 | public async Task Dequeue(TimeSpan timeout) 25 | { 26 | while (states.Count == 0) 27 | { 28 | if (!input_exhausted_task.Task.IsCompleted) 29 | input_exhausted_task.SetResult(true); 30 | await Task.WhenAny(input_ready_task.Task, Task.Delay(timeout)); 31 | } 32 | 33 | return DequeueInternal(); 34 | } 35 | 36 | public async Task Dequeue() 37 | { 38 | while (states.Count == 0) 39 | { 40 | if (!input_exhausted_task.Task.IsCompleted) 41 | input_exhausted_task.SetResult(true); 42 | await input_ready_task.Task; 43 | } 44 | 45 | return DequeueInternal(); 46 | } 47 | 48 | public async Task Dequeue(int timeout_ms) 49 | { 50 | while (states.Count == 0) 51 | { 52 | if (!input_exhausted_task.Task.IsCompleted) 53 | input_exhausted_task.SetResult(true); 54 | await Task.WhenAny(input_ready_task.Task, Task.Delay(timeout_ms)); 55 | } 56 | 57 | return DequeueInternal(); 58 | } 59 | 60 | 61 | public void Enqueue(InputState state) 62 | { 63 | states.Enqueue(state); 64 | 65 | if (input_exhausted_task.Task.IsCompleted) 66 | input_exhausted_task = new TaskCompletionSource(); 67 | if (!input_ready_task.Task.IsCompleted) 68 | input_ready_task.SetResult(true); 69 | } 70 | 71 | public Task WaitForConsumer() 72 | { 73 | return input_exhausted_task.Task; 74 | } 75 | 76 | public Task WaitForInput() 77 | { 78 | if (states.Count != 0) 79 | return Task.CompletedTask; 80 | if (!input_exhausted_task.Task.IsCompleted) 81 | input_exhausted_task.SetResult(true); 82 | return input_ready_task.Task; 83 | } 84 | 85 | public bool IsEmpty 86 | { 87 | get 88 | { 89 | return states.Count == 0; 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /xalia/Input/VirtualInputBackend.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Xalia.Input 4 | { 5 | internal class VirtualInputBackend : InputBackend 6 | { 7 | Dictionary> input_sinks = new Dictionary>(); 8 | 9 | protected internal override InputState GetActionState(string action) 10 | { 11 | InputState result = default; 12 | 13 | if (input_sinks.TryGetValue(action, out var sinks)) 14 | { 15 | foreach (var sink in sinks) 16 | { 17 | result = InputState.Combine(result, sink.GetState()); 18 | } 19 | } 20 | 21 | return result; 22 | } 23 | 24 | new internal void ActionStateUpdated(string action) 25 | { 26 | base.ActionStateUpdated(action); 27 | } 28 | 29 | internal VirtualInputSink AddInputSink(string action) 30 | { 31 | var result = new VirtualInputSink(action); 32 | 33 | List sinks; 34 | 35 | if (!input_sinks.TryGetValue(action, out sinks)) 36 | { 37 | sinks = new List(); 38 | input_sinks[action] = sinks; 39 | } 40 | 41 | sinks.Add(result); 42 | 43 | return result; 44 | } 45 | 46 | internal void RemoveInputSink(VirtualInputSink sink) 47 | { 48 | var sinks = input_sinks[sink.Action]; 49 | sinks.Remove(sink); 50 | ActionStateUpdated(sink.Action); 51 | // ActionMappingUpdated(sink.Action); 52 | } 53 | 54 | protected internal override bool UnwatchAction(string name) 55 | { 56 | return input_sinks.TryGetValue(name, out var sinks) && sinks.Count != 0; 57 | } 58 | 59 | protected internal override bool WatchAction(string name) 60 | { 61 | return UnwatchAction(name); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /xalia/Input/VirtualInputSink.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Xalia.Input 5 | { 6 | public class VirtualInputSink : IDisposable 7 | { 8 | public string Action { get; } 9 | 10 | InputState current_state; 11 | 12 | internal VirtualInputSink(string action) 13 | { 14 | Action = action; 15 | } 16 | 17 | public void Dispose() 18 | { 19 | InputSystem.Instance.VirtualInputBackend.RemoveInputSink(this); 20 | } 21 | 22 | internal InputState GetState() 23 | { 24 | return current_state; 25 | } 26 | 27 | public void SetInputState(InputState state) 28 | { 29 | current_state = state; 30 | InputSystem.Instance.VirtualInputBackend.ActionStateUpdated(Action); 31 | } 32 | 33 | public void SendInputStates(IEnumerable states) 34 | { 35 | foreach (var state in states) 36 | { 37 | SetInputState(state); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /xalia/Interop/Win32WaitHandle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Xalia.Interop 11 | { 12 | internal class Win32WaitHandle : WaitHandle 13 | { 14 | public Win32WaitHandle(SafeWaitHandle handle, bool ownHandle) 15 | { 16 | SafeWaitHandle = handle; 17 | OwnHandle = ownHandle; 18 | } 19 | 20 | public bool OwnHandle { get; } 21 | 22 | protected override void Dispose(bool explicitDisposing) 23 | { 24 | if (OwnHandle) 25 | { 26 | base.Dispose(explicitDisposing); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /xalia/Interop/X11.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | using static SDL2.SDL; 5 | 6 | namespace Xalia.Interop 7 | { 8 | internal static class X11 9 | { 10 | const string X11_LIB = "X11"; 11 | const string XTEST_LIB = "Xtst"; 12 | 13 | public static IntPtr XA_RESOURCE_MANAGER => (IntPtr)23; 14 | public static IntPtr XA_STRING => (IntPtr)31; 15 | 16 | 17 | [StructLayout(LayoutKind.Sequential)] 18 | public struct XWindowAttributes 19 | { 20 | public int x; 21 | public int y; 22 | public int width; 23 | public int height; 24 | public int border_width; 25 | public int depth; 26 | public IntPtr visual; 27 | public IntPtr root; 28 | public int _class; 29 | public int bit_gravity; 30 | public int win_gravity; 31 | public int backing_store; 32 | public UIntPtr backing_planes; 33 | public UIntPtr backing_pixel; 34 | public int save_under; 35 | public IntPtr colormap; 36 | public int map_installed; 37 | public int map_state; 38 | public IntPtr all_event_masks; 39 | public IntPtr your_event_mask; 40 | public IntPtr do_not_propogate_mask; 41 | public int override_redirect; 42 | public IntPtr screen; 43 | } 44 | 45 | [StructLayout(LayoutKind.Sequential)] 46 | public struct XPropertyEvent 47 | { 48 | public int type; 49 | public UIntPtr serial; 50 | public int send_event; // Bool 51 | public IntPtr display; // Display* 52 | public IntPtr window; // Window 53 | public IntPtr atom; // Atom 54 | public IntPtr time; // Time 55 | public int state; 56 | } 57 | 58 | [StructLayout(LayoutKind.Sequential)] 59 | public struct SDL_SysWMmsg_X11 60 | { 61 | public SDL_version version; 62 | public SDL_SYSWM_TYPE subsystem; 63 | public XEvent xev; 64 | } 65 | 66 | [StructLayout(LayoutKind.Sequential)] 67 | public struct XEventPad 68 | { 69 | IntPtr pad0; 70 | IntPtr pad1; 71 | IntPtr pad2; 72 | IntPtr pad3; 73 | IntPtr pad4; 74 | IntPtr pad5; 75 | IntPtr pad6; 76 | IntPtr pad7; 77 | IntPtr pad8; 78 | IntPtr pad9; 79 | IntPtr pad10; 80 | IntPtr pad11; 81 | IntPtr pad12; 82 | IntPtr pad13; 83 | IntPtr pad14; 84 | IntPtr pad15; 85 | IntPtr pad16; 86 | IntPtr pad17; 87 | IntPtr pad18; 88 | IntPtr pad19; 89 | IntPtr pad20; 90 | IntPtr pad21; 91 | IntPtr pad22; 92 | IntPtr pad23; 93 | } 94 | 95 | [StructLayout(LayoutKind.Explicit)] 96 | public struct XEvent 97 | { 98 | [FieldOffset(0)] 99 | public int type; 100 | [FieldOffset(0)] 101 | public XPropertyEvent xproperty; 102 | [FieldOffset(0)] 103 | XEventPad pad; 104 | } 105 | 106 | public const int Success = 0; 107 | public const int True = 1; 108 | public const int False = 0; 109 | 110 | public const int PropertyNotify = 28; 111 | 112 | public static IntPtr PropertyChangeMask => (IntPtr)(1 << 22); 113 | 114 | [DllImport(X11_LIB)] 115 | public extern static IntPtr XDefaultRootWindow(IntPtr display); 116 | 117 | [DllImport(X11_LIB)] 118 | public extern static int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes window_attributes_return); 119 | 120 | [DllImport(X11_LIB)] 121 | public extern static int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr property, 122 | IntPtr long_offset, IntPtr long_length, int delete, IntPtr req_type, out IntPtr actual_type_return, 123 | out int actual_format_return, out UIntPtr nitems_return, out UIntPtr bytes_after_return, 124 | out IntPtr prop_return); 125 | 126 | [DllImport(X11_LIB)] 127 | public extern static int XFree(IntPtr data); 128 | 129 | [DllImport(X11_LIB, CharSet = CharSet.Ansi)] 130 | public extern static IntPtr XInternAtom(IntPtr display, string atom_name, int only_if_exists); 131 | 132 | [DllImport(X11_LIB)] 133 | public extern static int XSelectInput(IntPtr display, IntPtr window, IntPtr event_mask); 134 | 135 | [DllImport(X11_LIB)] 136 | public extern static IntPtr XKeysymToKeycode(IntPtr display, IntPtr keysym); 137 | 138 | [DllImport(XTEST_LIB)] 139 | public extern static int XTestQueryExtension(IntPtr display, ref int event_basep, ref int error_basep, ref int majorp, ref int minorp); 140 | 141 | [DllImport(XTEST_LIB)] 142 | public extern static int XTestFakeKeyEvent(IntPtr display, int keycode, int is_press, IntPtr delay); 143 | 144 | [DllImport(XTEST_LIB)] 145 | public extern static int XTestFakeButtonEvent(IntPtr display, int button, int is_press, IntPtr delay); 146 | 147 | [DllImport(XTEST_LIB)] 148 | public extern static int XTestFakeMotionEvent(IntPtr display, int screen, int x, int y, IntPtr delay); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /xalia/MainClass.cs: -------------------------------------------------------------------------------- 1 | #if WINDOWS 2 | using Microsoft.Win32.SafeHandles; 3 | #endif 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Runtime.InteropServices; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Xalia.Gudl; 12 | using Xalia.Ui; 13 | using Xalia.UiDom; 14 | using Xalia.Sdl; 15 | 16 | using static SDL2.SDL; 17 | #if WINDOWS 18 | using static Xalia.Interop.Win32; 19 | using Xalia.Win32; 20 | #endif 21 | 22 | namespace Xalia 23 | { 24 | public class MainClass 25 | { 26 | #if WINDOWS 27 | static async Task MakeWineSystemProcess() 28 | { 29 | int status = NtSetInformationProcess(GetCurrentProcess(), 30 | PROCESSINFOCLASS.ProcessWineMakeProcessSystem, 31 | out SafeWaitHandle exit_event, IntPtr.Size); 32 | 33 | Marshal.ThrowExceptionForHR(status); 34 | 35 | await Utils.WaitAsync(exit_event, true); 36 | 37 | Environment.Exit(0); 38 | } 39 | #endif 40 | 41 | static async Task Init(GudlStatement[] config) 42 | { 43 | try 44 | { 45 | var application = new UiMain(); 46 | 47 | UiDomRoot connection = null; 48 | 49 | if (connection == null && 50 | ((Environment.GetEnvironmentVariable("XALIA_USE_ATSPI") ?? (Utils.IsUnix() ? "1" : "0")) != "0")) 51 | { 52 | connection = new UiDomRoot(config, application); 53 | await Xalia.AtSpi2.AtSpiConnection.Connect(connection); 54 | } 55 | 56 | #if WINDOWS 57 | if (connection == null && 58 | ((Environment.GetEnvironmentVariable("XALIA_USE_WIN32") ?? (Utils.IsWindows() ? "1" : "0")) != "0")) 59 | { 60 | connection = new UiDomRoot(config, application); 61 | new Win32Connection(connection); 62 | } 63 | #endif 64 | 65 | if (connection == null) 66 | { 67 | Utils.DebugWriteLine("No Accessibility API available"); 68 | } 69 | 70 | GameControllerInput.Init(); 71 | 72 | #if WINFORMS 73 | if ((Environment.GetEnvironmentVariable("XALIA_DEBUG_TREEVIEW") ?? "0") == "1") 74 | { 75 | var main_context = SynchronizationContext.Current; 76 | Thread treeview_thread = new Thread(() => 77 | { 78 | Xalia.Viewer.UiDomViewer.ThreadProc(main_context, connection); 79 | }); 80 | treeview_thread.SetApartmentState(ApartmentState.STA); 81 | treeview_thread.Start(); 82 | } 83 | #endif 84 | } 85 | catch (Exception e) 86 | { 87 | Utils.DebugWriteLine(e); 88 | Environment.Exit(1); 89 | } 90 | } 91 | 92 | [STAThread()] 93 | public static int Main(string[] argv) 94 | { 95 | AppDomain.CurrentDomain.UnhandledException += Utils.UnhandledException; 96 | 97 | #if NETCOREAPP3_0_OR_GREATER 98 | NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), OnDllImport); 99 | #endif 100 | 101 | SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); 102 | 103 | SdlSynchronizationContext.Instance.Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); 104 | 105 | #if WINDOWS 106 | if (Utils.IsWindows() && 107 | (argv.Contains("-wineSystemProcess") || 108 | (Utils.TryGetEnvironmentVariable("XALIA_WINE_SYSTEM_PROCESS", out var system_process) && system_process != "0"))) 109 | { 110 | Utils.RunTask(MakeWineSystemProcess()); 111 | } 112 | #endif 113 | 114 | GudlStatement[] config; 115 | 116 | if (!GudlParser.TryParse( 117 | Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "main.gudl"), 118 | out config, out var error)) 119 | { 120 | Utils.DebugWriteLine(error); 121 | } 122 | 123 | Utils.RunTask(Init(config)); 124 | 125 | SdlSynchronizationContext.Instance.MainLoop(); 126 | 127 | return 0; 128 | } 129 | 130 | #if NETCOREAPP3_0_OR_GREATER 131 | private static IntPtr OnDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) 132 | { 133 | string actual_library = null; 134 | 135 | switch ($"{Environment.OSVersion.Platform}:{libraryName}") 136 | { 137 | case "Win32NT:SDL2": 138 | actual_library = "SDL2.dll"; 139 | break; 140 | case "Unix:SDL2": 141 | actual_library = "libSDL2-2.0.so.0"; 142 | break; 143 | case "MacOSX:SDL2": 144 | actual_library = "libSDL2-2.0.0.dylib"; 145 | break; 146 | case "Unix:X11": 147 | actual_library = "libX11.so.6"; 148 | break; 149 | case "Unix:Xtst": 150 | actual_library = "libXtst.so.6"; 151 | break; 152 | } 153 | 154 | if (!(actual_library is null) && NativeLibrary.TryLoad(actual_library, assembly, searchPath, out IntPtr result)) 155 | return result; 156 | 157 | return IntPtr.Zero; 158 | } 159 | #endif 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /xalia/Sdl/OverlayBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using static SDL2.SDL; 4 | 5 | namespace Xalia.Sdl 6 | { 7 | internal abstract class OverlayBox : IDisposable 8 | { 9 | public OverlayBox(WindowingSystem windowingSystem) 10 | { 11 | WindowingSystem = windowingSystem; 12 | } 13 | 14 | private WindowingSystem WindowingSystem { get; } 15 | 16 | protected abstract void Dispose(bool disposing); 17 | 18 | public void Dispose() 19 | { 20 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 21 | Dispose(disposing: true); 22 | GC.SuppressFinalize(this); 23 | } 24 | 25 | [Flags] 26 | public enum UpdateFlags 27 | { 28 | Visible = 0x1, 29 | Show = 0x2, 30 | SizeChanged = 0x4, 31 | PositionChanged = 0x8, 32 | ThicknessChanged = 0x10, 33 | EffectiveThicknessChanged = 0x20, 34 | ColorChanged = 0x40, 35 | } 36 | 37 | protected abstract void Update(UpdateFlags flags); 38 | 39 | private void NotifyUpdate(UpdateFlags flags) 40 | { 41 | int new_effective_thickness = (int)Math.Round(_thickness * WindowingSystem.GetDpi(_x, _y) / 96); 42 | if (new_effective_thickness != EffectiveThickness) 43 | { 44 | EffectiveThickness = new_effective_thickness; 45 | flags |= UpdateFlags.EffectiveThicknessChanged; 46 | } 47 | if (Visible) 48 | flags |= UpdateFlags.Visible; 49 | Update(flags); 50 | } 51 | 52 | private bool _visible; 53 | 54 | public virtual bool Visible 55 | { 56 | get => _visible; 57 | set 58 | { 59 | if (_visible != value) 60 | { 61 | _visible = value; 62 | NotifyUpdate(_visible ? UpdateFlags.Show : default); 63 | } 64 | } 65 | } 66 | 67 | public void Show() 68 | { 69 | Visible = true; 70 | } 71 | 72 | public void Hide() 73 | { 74 | Visible = false; 75 | } 76 | 77 | private int _x, _y, _width, _height; 78 | 79 | public int X 80 | { 81 | get => _x; 82 | set 83 | { 84 | SdlSynchronizationContext.Instance.AssertMainThread(); 85 | if (_x != value) 86 | { 87 | _x = value; 88 | NotifyUpdate(UpdateFlags.PositionChanged); 89 | } 90 | } 91 | } 92 | public int Y 93 | { 94 | get => _y; 95 | set 96 | { 97 | SdlSynchronizationContext.Instance.AssertMainThread(); 98 | if (_y != value) 99 | { 100 | _y = value; 101 | NotifyUpdate(UpdateFlags.PositionChanged); 102 | } 103 | } 104 | } 105 | public int Width 106 | { 107 | get => _width; 108 | set 109 | { 110 | if (_width != value) 111 | { 112 | SdlSynchronizationContext.Instance.AssertMainThread(); 113 | NotifyUpdate(UpdateFlags.SizeChanged); 114 | } 115 | } 116 | } 117 | public int Height 118 | { 119 | get => _height; 120 | set 121 | { 122 | if (_height != value) 123 | { 124 | SdlSynchronizationContext.Instance.AssertMainThread(); 125 | _height = value; 126 | NotifyUpdate(UpdateFlags.SizeChanged); 127 | } 128 | } 129 | } 130 | 131 | private SDL_Color _color; 132 | public SDL_Color Color 133 | { 134 | get => _color; 135 | set 136 | { 137 | if (!_color.Equals(value)) 138 | { 139 | _color = value; 140 | NotifyUpdate(UpdateFlags.ColorChanged); 141 | } 142 | } 143 | } 144 | 145 | public void SetColor(byte r, byte g, byte b, byte a) 146 | { 147 | var color = new SDL_Color(); 148 | color.r = r; 149 | color.g = g; 150 | color.b = b; 151 | color.a = a; 152 | Color = color; 153 | } 154 | 155 | public void SetBounds(int x, int y, int width, int height) 156 | { 157 | SdlSynchronizationContext.Instance.AssertMainThread(); 158 | 159 | UpdateFlags flags = default; 160 | if (x != _x || y != _y) 161 | flags |= UpdateFlags.PositionChanged; 162 | if (width != _width || height != _height) 163 | flags |= UpdateFlags.SizeChanged; 164 | 165 | _x = x; 166 | _y = y; 167 | _width = width; 168 | _height = height; 169 | 170 | if (flags != 0) 171 | NotifyUpdate(flags); 172 | } 173 | 174 | private int _thickness = 5; 175 | 176 | public int Thickness 177 | { 178 | get => _thickness; 179 | set 180 | { 181 | if (_thickness != value) 182 | { 183 | _thickness = value; 184 | NotifyUpdate(UpdateFlags.ThicknessChanged); 185 | } 186 | } 187 | } 188 | 189 | public int EffectiveThickness { get; private set; } = 5; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /xalia/Sdl/SdlSynchronizationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | 5 | using static SDL2.SDL; 6 | 7 | namespace Xalia.Sdl 8 | { 9 | internal class SdlSynchronizationContext : SynchronizationContext 10 | { 11 | public static SdlSynchronizationContext Instance { get; } = new SdlSynchronizationContext(); 12 | 13 | private object _main_thread_obj; 14 | 15 | public Thread MainThread { get; private set; } 16 | 17 | private bool _quitting; 18 | 19 | ConcurrentQueue<(SendOrPostCallback, object)> _posts = new ConcurrentQueue<(SendOrPostCallback, object)>(); 20 | 21 | private class SendCallback 22 | { 23 | public SendOrPostCallback callback; 24 | public object state; 25 | public EventWaitHandle completed_event; 26 | } 27 | 28 | ConcurrentQueue _sends = new ConcurrentQueue(); 29 | 30 | private uint _queue_updated_event; 31 | 32 | private SdlSynchronizationContext() 33 | { 34 | } 35 | 36 | public void Init(uint flags) 37 | { 38 | if (Interlocked.CompareExchange(ref _main_thread_obj, Thread.CurrentThread, null) != null) 39 | { 40 | throw new InvalidOperationException("Init called more than once"); 41 | } 42 | MainThread = Thread.CurrentThread; 43 | 44 | SetSynchronizationContext(this); 45 | 46 | SDL_SetMainReady(); 47 | 48 | SDL_Init(flags); 49 | 50 | _queue_updated_event = SDL_RegisterEvents(1); 51 | } 52 | 53 | public void Init() 54 | { 55 | Init(SDL_INIT_EVERYTHING); 56 | } 57 | 58 | public void AssertMainThread() 59 | { 60 | if (MainThread is null) 61 | throw new InvalidOperationException("SdlSynchronizationContext.Init must be called before this method"); 62 | if (Thread.CurrentThread != MainThread) 63 | throw new InvalidOperationException("must be called from main SDL thread"); 64 | } 65 | 66 | public void Quit() 67 | { 68 | AssertMainThread(); 69 | _quitting = true; 70 | SDL_Quit(); 71 | } 72 | 73 | public class SdlEventArgs : EventArgs 74 | { 75 | public SdlEventArgs(SDL_Event sdl_event) 76 | { 77 | SdlEvent = sdl_event; 78 | } 79 | 80 | public bool Cancel { get; set; } 81 | public SDL_Event SdlEvent { get; } 82 | } 83 | 84 | public delegate void SdlEventHandler(object sender, SdlEventArgs e); 85 | 86 | public event SdlEventHandler SdlEvent; 87 | 88 | public void MainLoop() 89 | { 90 | AssertMainThread(); 91 | while (!_quitting) 92 | { 93 | if (_sends.TryDequeue(out var send)) 94 | { 95 | send.callback(send.state); 96 | send.completed_event.Set(); 97 | continue; 98 | } 99 | if (SDL_PollEvent(out var poll_e) != 0) 100 | { 101 | try 102 | { 103 | HandleEvent(poll_e); 104 | } 105 | catch (Exception e) 106 | { 107 | Utils.OnError(e); 108 | } 109 | continue; 110 | } 111 | if (_posts.TryDequeue(out var post)) 112 | { 113 | post.Item1(post.Item2); 114 | continue; 115 | } 116 | if (SDL_WaitEvent(out var wait_e) != 0) 117 | { 118 | HandleEvent(wait_e); 119 | continue; 120 | } 121 | } 122 | } 123 | 124 | private void HandleEvent(SDL_Event e) 125 | { 126 | var handler = SdlEvent; 127 | var eventargs = new SdlEventArgs(e); 128 | if (handler != null) 129 | handler(this, eventargs); 130 | if (eventargs.Cancel) 131 | return; 132 | if (e.type == SDL_EventType.SDL_QUIT) 133 | Quit(); 134 | } 135 | 136 | private void NotifyQueue(bool force) 137 | { 138 | if (Thread.CurrentThread == MainThread && !force) 139 | return; 140 | SDL_Event e = new SDL_Event(); 141 | e.type = (SDL_EventType)_queue_updated_event; 142 | SDL_PushEvent(ref e); 143 | } 144 | 145 | public override void Post(SendOrPostCallback d, object state) 146 | { 147 | _posts.Enqueue((d, state)); 148 | NotifyQueue(_posts.Count == 1); 149 | } 150 | 151 | public override void Send(SendOrPostCallback d, object state) 152 | { 153 | if (Thread.CurrentThread == MainThread) 154 | { 155 | d(state); 156 | return; 157 | } 158 | var callback = new SendCallback(); 159 | callback.callback = d; 160 | callback.state = state; 161 | callback.completed_event = new EventWaitHandle(false, EventResetMode.ManualReset); 162 | 163 | _sends.Enqueue(callback); 164 | 165 | NotifyQueue(false); 166 | 167 | callback.completed_event.WaitOne(); 168 | callback.completed_event.Dispose(); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /xalia/Sdl/XdgWindowingSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | 5 | namespace Xalia.Sdl 6 | { 7 | internal class XdgWindowingSystem : WindowingSystem 8 | { 9 | public override bool CanShowKeyboard() 10 | { 11 | return true; 12 | } 13 | 14 | public override Task ShowKeyboardAsync() 15 | { 16 | // This is pretty low-effort, but we don't have any suitable keyboards available on XDG 17 | try 18 | { 19 | Process.Start("onboard"); 20 | } 21 | catch (Exception e) 22 | { 23 | Utils.OnError(e); 24 | } 25 | 26 | return Task.CompletedTask; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /xalia/Ui/CurrentViewRoutine.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xalia.Input; 3 | using Xalia.UiDom; 4 | 5 | namespace Xalia.Ui 6 | { 7 | internal class CurrentViewRoutine : UiDomRoutine 8 | { 9 | public CurrentViewRoutine(UiMain main, UiDomRoutine routine) : 10 | base("wrap_current_view_action", new UiDomValue[] { routine }) 11 | { 12 | Main = main; 13 | Routine = routine; 14 | } 15 | 16 | public UiMain Main { get; } 17 | public UiDomRoutine Routine { get; } 18 | 19 | public override async Task ProcessInputQueue(InputQueue queue) 20 | { 21 | Main.CurrentViewRoutineStarted(); 22 | try 23 | { 24 | await Routine.ProcessInputQueue(queue); 25 | } 26 | finally 27 | { 28 | Main.CurrentViewRoutineStopped(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /xalia/Ui/SendKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Runtime.InteropServices; 5 | using Xalia.Gudl; 6 | using Xalia.Sdl; 7 | using Xalia.UiDom; 8 | 9 | namespace Xalia.Ui 10 | { 11 | internal class SendKey : UiDomValue 12 | { 13 | public SendKey(WindowingSystem windowing) 14 | { 15 | Windowing = windowing; 16 | } 17 | 18 | public WindowingSystem Windowing { get; } 19 | 20 | public override string ToString() 21 | { 22 | return "send_key"; 23 | } 24 | 25 | private UiDomRoutineAsync RoutineForKeyCode(int keycode, string name) 26 | { 27 | return new UiDomRoutineAsync(null, name, async (UiDomRoutineAsync rou) => 28 | { 29 | await Windowing.SendKey(keycode); 30 | }); 31 | } 32 | 33 | protected override UiDomValue EvaluateDot(UiDomValue context, GudlExpression expr, 34 | UiDomRoot root, [In][Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 35 | { 36 | if (expr is StringExpression st) 37 | { 38 | return EvaluateIdentifier(st.Value, root, depends_on); 39 | } 40 | if (expr is IntegerExpression i) 41 | { 42 | try 43 | { 44 | return RoutineForKeyCode((int)i.Value, $"send_key.{i.Value.ToString(CultureInfo.InvariantCulture)}"); 45 | } 46 | catch (OverflowException) 47 | { 48 | return UiDomUndefined.Instance; 49 | } 50 | } 51 | return Evaluate(expr, root, depends_on); 52 | } 53 | 54 | protected override UiDomValue EvaluateApply(UiDomValue context, GudlExpression[] arglist, 55 | UiDomRoot root, [In][Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 56 | { 57 | if (arglist.Length != 1) 58 | return UiDomUndefined.Instance; 59 | var expr = arglist[0]; 60 | UiDomValue right = context.Evaluate(expr, root, depends_on); 61 | if (right is UiDomString st) 62 | { 63 | return EvaluateIdentifier(st.Value, root, depends_on); 64 | } 65 | if (right.TryToInt(out int i)) 66 | { 67 | return RoutineForKeyCode(i, $"send_key.{i.ToString(CultureInfo.InvariantCulture)}"); 68 | } 69 | return UiDomUndefined.Instance; 70 | } 71 | 72 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 73 | { 74 | var keycode = Windowing.GetKeySym(id); 75 | 76 | if (keycode != 0) 77 | { 78 | return RoutineForKeyCode(keycode, $"send_key.{id.ToString(CultureInfo.InvariantCulture)}"); 79 | } 80 | 81 | return base.EvaluateIdentifierCore(id, root, depends_on); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /xalia/Ui/SendScroll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Xalia.Input; 5 | using Xalia.Sdl; 6 | using Xalia.UiDom; 7 | 8 | namespace Xalia.Ui 9 | { 10 | internal class SendScroll : UiDomRoutine 11 | { 12 | public SendScroll(UiDomElement element, WindowingSystem windowing) : base(element, "send_scroll") 13 | { 14 | Windowing = windowing; 15 | } 16 | 17 | private static readonly double xscale = 100.0 / 6 / 32767; 18 | private static readonly double yscale = 100.0 / 6 / 32767; 19 | private static readonly long delay_ticks = Stopwatch.Frequency / 120; 20 | 21 | public WindowingSystem Windowing { get; } 22 | 23 | public override async Task ProcessInputQueue(InputQueue queue) 24 | { 25 | var stopwatch = new Stopwatch(); 26 | InputState prev_state = new InputState(InputStateKind.Disconnected), state; 27 | long last_repeat = 0; 28 | double xremainder=0, yremainder=0; 29 | while (true) 30 | { 31 | state = await queue.Dequeue(); 32 | if (state.Kind == InputStateKind.Disconnected) 33 | break; 34 | if ((state.Kind == InputStateKind.Pulse || state.Kind == InputStateKind.Repeat) && 35 | prev_state.Kind == InputStateKind.AnalogJoystick) 36 | { 37 | (xremainder, yremainder) = await DoAdjustment(prev_state, xscale, yscale, xremainder, yremainder); 38 | if (stopwatch.IsRunning) 39 | stopwatch.Reset(); 40 | } 41 | else if (state.Kind == InputStateKind.AnalogJoystick && state.Intensity >= 1000) 42 | { 43 | if (!stopwatch.IsRunning) 44 | { 45 | stopwatch.Start(); 46 | last_repeat = 0; 47 | (xremainder, yremainder) = await DoAdjustment(prev_state, xscale, yscale, xremainder, yremainder); 48 | } 49 | while (queue.IsEmpty) 50 | { 51 | var elapsed_ticks = stopwatch.ElapsedTicks - last_repeat; 52 | if (elapsed_ticks < delay_ticks) 53 | { 54 | await Task.WhenAny(queue.WaitForInput(), Task.Delay(TimeSpan.FromSeconds((delay_ticks - elapsed_ticks) / (double)Stopwatch.Frequency))); 55 | continue; 56 | } 57 | long num_steps = elapsed_ticks / delay_ticks; 58 | 59 | (xremainder, yremainder) = await DoAdjustment(prev_state, 60 | Math.Min(num_steps, 60) * xscale, Math.Min(num_steps, 60) * yscale, 61 | xremainder, yremainder); 62 | last_repeat += delay_ticks * num_steps; 63 | } 64 | } 65 | else if (state.Kind == InputStateKind.PixelDelta && state.Intensity > 0) 66 | { 67 | (xremainder, yremainder) = await DoAdjustment(state, 1, 1, xremainder, yremainder); 68 | } 69 | else 70 | { 71 | stopwatch.Reset(); 72 | } 73 | prev_state = state; 74 | } 75 | } 76 | 77 | private async Task<(double,double)> DoAdjustment(InputState state, double xmult, double ymult, double xremainder, double yremainder) 78 | { 79 | double xofs = state.XAxis * xmult + xremainder; 80 | double yofs = state.YAxis * ymult + yremainder; 81 | 82 | double xadjustment = Math.Truncate(xofs); 83 | double yadjustment = Math.Truncate(yofs); 84 | 85 | if (xadjustment == 0 && yadjustment == 0) 86 | { 87 | return (xofs, yofs); 88 | } 89 | 90 | var position = await Element.GetClickablePoint(); 91 | 92 | if (!position.Item1) 93 | { 94 | Utils.DebugWriteLine($"WARNING: Could not get clickable point for {Element}"); 95 | return (xofs - xadjustment, yofs - yadjustment); 96 | } 97 | 98 | try 99 | { 100 | await Windowing.SendMouseMotion(position.Item2, position.Item3); 101 | 102 | await Windowing.SendScroll((int)xadjustment, (int)yadjustment); 103 | } 104 | catch (NotImplementedException) 105 | { 106 | Utils.DebugWriteLine($"WARNING: Cannot send mouse scroll events on the current windowing system"); 107 | } 108 | 109 | return (xofs - xadjustment, yofs - yadjustment); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /xalia/Ui/TargetMoveButtonRoutine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xalia.Input; 4 | using Xalia.UiDom; 5 | 6 | namespace Xalia.Ui 7 | { 8 | internal class TargetMoveButtonRoutine : UiDomRoutineSync 9 | { 10 | public TargetMoveButtonRoutine(UiMain main, string name, Action action) 11 | : base(name, action) 12 | { 13 | Main = main; 14 | } 15 | 16 | public UiMain Main { get; } 17 | 18 | public override async Task ProcessInputQueue(InputQueue queue) 19 | { 20 | Main.TargetMoveRoutineStarted(); 21 | try 22 | { 23 | await base.ProcessInputQueue(queue); 24 | } 25 | finally 26 | { 27 | Main.TargetMoveRoutineStopped(); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /xalia/Ui/TargetMoveRoutine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xalia.Input; 4 | using Xalia.UiDom; 5 | 6 | namespace Xalia.Ui 7 | { 8 | internal class TargetMoveRoutine : UiDomRoutine 9 | { 10 | public TargetMoveRoutine(UiMain main) : base("target_move") 11 | { 12 | Main = main; 13 | } 14 | 15 | public UiMain Main { get; } 16 | 17 | public override async Task ProcessInputQueue(InputQueue queue) 18 | { 19 | Main.TargetMoveRoutineStarted(); 20 | try 21 | { 22 | InputState prev_state = new InputState(InputStateKind.Disconnected), state; 23 | do 24 | { 25 | state = await queue.Dequeue(); 26 | if (state.JustPressed(prev_state)) 27 | { 28 | if (state.Kind == InputStateKind.AnalogJoystick) 29 | DoMove(state); 30 | else if (prev_state.Kind == InputStateKind.AnalogJoystick) 31 | DoMove(prev_state); 32 | } 33 | prev_state = state; 34 | } while (state.Kind != InputStateKind.Disconnected); 35 | } 36 | finally 37 | { 38 | Main.TargetMoveRoutineStopped(); 39 | } 40 | } 41 | 42 | private void DoMove(InputState state) 43 | { 44 | if (Math.Abs((int)state.XAxis) > Math.Abs((int)state.YAxis)) 45 | { 46 | if (state.XAxis > 0) 47 | Main.TargetMove(UiMain.Direction.Right); 48 | else 49 | Main.TargetMove(UiMain.Direction.Left); 50 | } 51 | else 52 | { 53 | if (state.YAxis > 0) 54 | Main.TargetMove(UiMain.Direction.Down); 55 | else 56 | Main.TargetMove(UiMain.Direction.Up); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /xalia/UiDom/ExpressionWatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Xalia.Gudl; 5 | 6 | namespace Xalia.UiDom 7 | { 8 | public class ExpressionWatcher : IDisposable 9 | { 10 | public ExpressionWatcher(UiDomValue context, UiDomRoot root, GudlExpression expression) 11 | { 12 | Context = context; 13 | Root = root; 14 | Expression = expression; 15 | 16 | UpdateCurrentValue(); 17 | } 18 | 19 | public UiDomValue Context { get; } 20 | public UiDomRoot Root { get; } 21 | public GudlExpression Expression { get; } 22 | 23 | public UiDomValue CurrentValue { get; private set; } = UiDomUndefined.Instance; 24 | 25 | private Dictionary<(UiDomElement, GudlExpression), IDisposable> notifiers = new Dictionary<(UiDomElement, GudlExpression), IDisposable>(); 26 | private bool disposedValue; 27 | 28 | private TaskCompletionSource changed_task; 29 | 30 | public event EventHandler ValueChanged; 31 | 32 | private void UpdateCurrentValue() 33 | { 34 | var depends_on = new HashSet<(UiDomElement, GudlExpression)>(); 35 | var value = Context.Evaluate(Expression, Root, depends_on); 36 | 37 | var updated_dependency_notifiers = new Dictionary<(UiDomElement, GudlExpression), IDisposable>(); 38 | foreach (var dep in depends_on) 39 | { 40 | if (notifiers.TryGetValue(dep, out var notifier)) 41 | { 42 | updated_dependency_notifiers[dep] = notifier; 43 | notifiers.Remove(dep); 44 | } 45 | else 46 | { 47 | updated_dependency_notifiers.Add(dep, 48 | dep.Item1.NotifyPropertyChanged(dep.Item2, OnDependencyChanged)); 49 | } 50 | } 51 | foreach (var notifier in notifiers.Values) 52 | { 53 | notifier.Dispose(); 54 | } 55 | notifiers = updated_dependency_notifiers; 56 | 57 | if (!CurrentValue.Equals(value)) 58 | { 59 | CurrentValue = value; 60 | if (ValueChanged != null) 61 | ValueChanged(this, new EventArgs()); 62 | if (changed_task != null) 63 | { 64 | var task = changed_task; 65 | changed_task = null; 66 | task.SetResult(false); 67 | } 68 | } 69 | } 70 | 71 | private void OnDependencyChanged(UiDomElement element, GudlExpression property) 72 | { 73 | UpdateCurrentValue(); 74 | } 75 | 76 | protected virtual void Dispose(bool disposing) 77 | { 78 | if (!disposedValue) 79 | { 80 | if (disposing) 81 | { 82 | foreach (var notifier in notifiers.Values) 83 | { 84 | notifier.Dispose(); 85 | } 86 | notifiers.Clear(); 87 | if (!(changed_task is null)) 88 | changed_task.SetCanceled(); 89 | } 90 | 91 | disposedValue = true; 92 | } 93 | } 94 | 95 | public void Dispose() 96 | { 97 | Dispose(disposing: true); 98 | } 99 | 100 | public Task WaitChanged() 101 | { 102 | if (changed_task is null) 103 | { 104 | changed_task = new TaskCompletionSource(); 105 | } 106 | return changed_task.Task; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /xalia/UiDom/IUiDomApplication.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices; 3 | 4 | using Xalia.Gudl; 5 | 6 | namespace Xalia.UiDom 7 | { 8 | public interface IUiDomApplication 9 | { 10 | void RootElementCreated(UiDomRoot root); 11 | 12 | void ElementDeclarationsChanged(UiDomElement element); 13 | 14 | void ElementDied(UiDomElement element); 15 | 16 | UiDomValue EvaluateIdentifierHook(UiDomElement element, string id, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on); 17 | void DumpElementProperties(UiDomElement uiDomElement); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /xalia/UiDom/IUiDomProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Xalia.Gudl; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | public interface IUiDomProvider 8 | { 9 | void NotifyElementRemoved(UiDomElement element); 10 | 11 | // returns True if handled 12 | bool WatchProperty(UiDomElement element, GudlExpression expression); 13 | 14 | bool UnwatchProperty(UiDomElement element, GudlExpression expression); 15 | 16 | UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, 17 | HashSet<(UiDomElement, GudlExpression)> depends_on); 18 | 19 | UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, 20 | HashSet<(UiDomElement, GudlExpression)> depends_on); 21 | 22 | void TrackedPropertyChanged(UiDomElement element, string name, UiDomValue new_value); 23 | 24 | void DumpProperties(UiDomElement element); 25 | 26 | Task<(bool, int, int)> GetClickablePointAsync(UiDomElement element); 27 | 28 | string[] GetTrackedProperties(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /xalia/UiDom/IUiDomScrollToProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Xalia.UiDom 4 | { 5 | internal interface IUiDomScrollToProvider : IUiDomProvider 6 | { 7 | Task ScrollToAsync(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /xalia/UiDom/IUiDomValueProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Xalia.UiDom 4 | { 5 | internal interface IUiDomValueProvider : IUiDomProvider 6 | { 7 | Task GetMinimumIncrementAsync(UiDomElement element); 8 | 9 | Task OffsetValueAsync(UiDomElement element, double offset); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomAdjustScrollbars.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Xalia.Input; 5 | 6 | namespace Xalia.UiDom 7 | { 8 | internal class UiDomAdjustScrollbars : UiDomRoutine 9 | { 10 | public UiDomAdjustScrollbars(UiDomElement hscroll, UiDomElement vscroll) 11 | { 12 | HScroll = hscroll; 13 | VScroll = vscroll; 14 | } 15 | 16 | public UiDomElement HScroll { get; } 17 | public UiDomElement VScroll { get; } 18 | 19 | private static readonly double xscale = 1.0 / 3 / 32767; 20 | private static readonly double yscale = 1.0 / 3 / 32767; 21 | private static readonly long delay_ticks = Stopwatch.Frequency / 120; 22 | 23 | public override bool Equals(object obj) 24 | { 25 | if (obj is UiDomAdjustScrollbars r) 26 | { 27 | return HScroll == r.HScroll && VScroll == r.VScroll; 28 | } 29 | return false; 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | return typeof(UiDomAdjustScrollbars).GetHashCode() ^ 35 | (HScroll, VScroll).GetHashCode(); 36 | } 37 | 38 | public override string ToString() 39 | { 40 | string h = HScroll is null ? "undefined" : HScroll.ToString(); 41 | string v = VScroll is null ? "undefined" : VScroll.ToString(); 42 | return $"adjust_scrollbars({h}, {v})"; 43 | } 44 | 45 | public override async Task ProcessInputQueue(InputQueue queue) 46 | { 47 | var stopwatch = new Stopwatch(); 48 | InputState prev_state = new InputState(InputStateKind.Disconnected), state; 49 | long last_repeat = 0; 50 | double xinc, yinc; 51 | double loc_xscale, loc_yscale; 52 | 53 | if (HScroll is null) 54 | xinc = 0; 55 | else 56 | xinc = await HScroll.GetMinimumIncrement(); 57 | 58 | if (VScroll is null) 59 | yinc = 0; 60 | else 61 | yinc = await VScroll.GetMinimumIncrement(); 62 | 63 | loc_xscale = xinc * xscale; 64 | loc_yscale = yinc * yscale; 65 | 66 | while (true) 67 | { 68 | state = await queue.Dequeue(); 69 | if (state.Kind == InputStateKind.Disconnected) 70 | break; 71 | if ((state.Kind == InputStateKind.Pulse || state.Kind == InputStateKind.Repeat) && 72 | prev_state.Kind == InputStateKind.AnalogJoystick) 73 | { 74 | await DoAdjustment(prev_state, loc_xscale, loc_yscale); 75 | if (stopwatch.IsRunning) 76 | stopwatch.Reset(); 77 | } 78 | else if (state.Kind == InputStateKind.AnalogJoystick && state.Intensity >= 1000) 79 | { 80 | if (!stopwatch.IsRunning) 81 | { 82 | stopwatch.Start(); 83 | last_repeat = 0; 84 | await DoAdjustment(state, loc_xscale, loc_yscale); 85 | } 86 | while (queue.IsEmpty) 87 | { 88 | var elapsed_ticks = stopwatch.ElapsedTicks - last_repeat; 89 | if (elapsed_ticks < delay_ticks) 90 | { 91 | await Task.WhenAny(queue.WaitForInput(), Task.Delay(TimeSpan.FromSeconds((delay_ticks - elapsed_ticks) / (double)Stopwatch.Frequency))); 92 | continue; 93 | } 94 | long num_steps = elapsed_ticks / delay_ticks; 95 | 96 | await DoAdjustment(state, Math.Min(num_steps, 60) * loc_xscale, Math.Min(num_steps, 60) * loc_yscale); 97 | last_repeat += delay_ticks * num_steps; 98 | } 99 | } 100 | else if (state.Kind == InputStateKind.PixelDelta && state.Intensity > 0) 101 | { 102 | await DoAdjustment(state, 1, 1); 103 | } 104 | else 105 | { 106 | stopwatch.Reset(); 107 | } 108 | prev_state = state; 109 | } 110 | } 111 | 112 | private async Task DoAdjustment(InputState state, double xmult, double ymult) 113 | { 114 | double xofs = state.XAxis * xmult; 115 | double yofs = state.YAxis * ymult; 116 | 117 | if (!(HScroll is null)) 118 | { 119 | await HScroll.OffsetValue(xofs); 120 | } 121 | 122 | if (!(VScroll is null)) 123 | { 124 | await VScroll.OffsetValue(yofs); 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomBoolean.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.UiDom 2 | { 3 | internal class UiDomBoolean : UiDomValue 4 | { 5 | private UiDomBoolean(bool value) 6 | { 7 | Value = value; 8 | if (value) 9 | stringval = "true"; 10 | else 11 | stringval = "false"; 12 | } 13 | 14 | public static UiDomBoolean True = new UiDomBoolean(true); 15 | public static UiDomBoolean False = new UiDomBoolean(false); 16 | 17 | public static UiDomBoolean FromBool(bool value) => value ? True : False; 18 | 19 | string stringval; 20 | 21 | public bool Value { get; } 22 | 23 | public override string ToString() 24 | { 25 | return stringval; 26 | } 27 | 28 | public override bool ToBool() 29 | { 30 | return Value; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomDoAction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices; 3 | using Xalia.Gudl; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | internal class UiDomDoAction : UiDomValue 8 | { 9 | private UiDomDoAction() 10 | { 11 | } 12 | 13 | public static UiDomDoAction Instance { get; } = new UiDomDoAction(); 14 | 15 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 16 | { 17 | return new UiDomDoActionRoutine(id); 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return "do_action"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomDoActionRoutine.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | using Xalia.Input; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | internal class UiDomDoActionRoutine : UiDomRoutine 8 | { 9 | public UiDomDoActionRoutine(string action) : base("do_action", new UiDomValue[] { new UiDomString(action) }) 10 | { 11 | Action = action; 12 | } 13 | 14 | public string Action { get; } 15 | 16 | public override async Task ProcessInputQueue(InputQueue queue) 17 | { 18 | using (var sink = InputSystem.Instance.CreateVirtualInput(Action)) 19 | { 20 | while (true) 21 | { 22 | var state = await queue.Dequeue(); 23 | 24 | if (state.Kind == InputStateKind.Disconnected) 25 | break; 26 | 27 | sink.SetInputState(state); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomDouble.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Numerics; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | internal class UiDomDouble : UiDomValue 8 | { 9 | public UiDomDouble(double value) 10 | { 11 | Value = value; 12 | } 13 | 14 | public double Value { get; } 15 | 16 | public override bool Equals(object obj) 17 | { 18 | if (ReferenceEquals(this, obj)) 19 | return true; 20 | if (obj is UiDomDouble d) 21 | return Value == d.Value; 22 | return base.Equals(obj); 23 | } 24 | 25 | public override bool Compare(UiDomValue other, out int sign) 26 | { 27 | if (other is UiDomDouble d) 28 | { 29 | if (Value == d.Value) 30 | sign = 0; 31 | else if (Value < d.Value) 32 | sign = -1; 33 | else 34 | sign = 1; 35 | return true; 36 | } 37 | if (other is UiDomInt i) 38 | { 39 | var tr = Math.Floor(Value); 40 | BigInteger bi; 41 | try 42 | { 43 | bi = new BigInteger(tr); 44 | } 45 | catch (OverflowException) 46 | { 47 | if (double.IsPositiveInfinity(tr)) 48 | { 49 | sign = 1; 50 | return true; 51 | } 52 | if (double.IsNegativeInfinity(tr)) 53 | { 54 | sign = -1; 55 | return true; 56 | } 57 | // else NaN 58 | sign = 0; 59 | return false; 60 | } 61 | 62 | if (bi == i.Value) 63 | { 64 | // Value floors to i.Value, therefore Value >= i.Value 65 | if (Value != tr) 66 | sign = 1; 67 | else 68 | sign = 0; 69 | } 70 | else if (bi < i.Value) 71 | sign = -1; 72 | else 73 | sign = 1; 74 | return true; 75 | } 76 | return base.Compare(other, out sign); 77 | } 78 | 79 | public override int GetHashCode() 80 | { 81 | return Value.GetHashCode() ^ typeof(UiDomDouble).GetHashCode(); 82 | } 83 | 84 | public override bool ToBool() 85 | { 86 | return Value != 0.0; 87 | } 88 | 89 | public override bool TryToInt(out int val) 90 | { 91 | try 92 | { 93 | val = (int)Math.Round(Value); 94 | return true; 95 | } 96 | catch (OverflowException) 97 | { 98 | val = 0; 99 | return false; 100 | } 101 | } 102 | 103 | public override bool TryToDouble(out double val) 104 | { 105 | val = Value; 106 | return true; 107 | } 108 | 109 | public override string ToString() 110 | { 111 | return Value.ToString(CultureInfo.InvariantCulture); 112 | } 113 | 114 | public override bool ValueEquals(UiDomValue other) 115 | { 116 | if (Equals(other)) 117 | return true; 118 | if (other is UiDomInt i) 119 | { 120 | var tr = Math.Floor(Value); 121 | if (tr != Value) 122 | return false; 123 | BigInteger bi; 124 | try 125 | { 126 | bi = new BigInteger(tr); 127 | } 128 | catch (OverflowException) 129 | { 130 | // Infinity or NaN 131 | return false; 132 | } 133 | 134 | return bi == i.Value; 135 | } 136 | return false; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomEnum.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using Xalia.Gudl; 6 | 7 | namespace Xalia.UiDom 8 | { 9 | internal class UiDomEnum : UiDomValue 10 | { 11 | public UiDomEnum(string[] names) 12 | { 13 | Names = names; 14 | } 15 | 16 | public string[] Names { get; } 17 | 18 | public override bool Equals(object obj) 19 | { 20 | if (obj is UiDomEnum en) 21 | { 22 | if (Names.Length != en.Names.Length) 23 | return false; 24 | foreach (string name in Names) 25 | { 26 | if (!en.Names.Contains(name)) 27 | return false; 28 | } 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | public override int GetHashCode() 35 | { 36 | var result = typeof(UiDomEnum).GetHashCode(); 37 | foreach (string name in Names) 38 | result ^= name.GetHashCode(); 39 | return result; 40 | } 41 | 42 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 43 | { 44 | switch (id) 45 | { 46 | case "name": 47 | return new UiDomString(Names[0]); 48 | } 49 | return UiDomBoolean.FromBool(Names.Contains(id)); 50 | } 51 | 52 | public override string ToString() 53 | { 54 | var sb = new StringBuilder(); 55 | sb.Append("enum(\""); 56 | sb.Append(Names[0]); 57 | sb.Append('"'); 58 | for (int i = 1; i < Names.Length; i++ ) 59 | { 60 | sb.Append(", \""); 61 | sb.Append(Names[i]); 62 | sb.Append('"'); 63 | } 64 | sb.Append(')'); 65 | return sb.ToString(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomEnviron.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using Xalia.Gudl; 5 | using Xalia.UiDom; 6 | 7 | namespace Xalia.UiDom 8 | { 9 | internal class UiDomEnviron : UiDomValue 10 | { 11 | private UiDomEnviron() 12 | { 13 | } 14 | 15 | public static readonly UiDomEnviron Instance = new UiDomEnviron(); 16 | 17 | public override bool Equals(object obj) 18 | { 19 | return ReferenceEquals(this, obj); 20 | } 21 | 22 | public override int GetHashCode() 23 | { 24 | return typeof(UiDomEnviron).GetHashCode(); 25 | } 26 | 27 | public override string ToString() 28 | { 29 | return "environ"; 30 | } 31 | 32 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 33 | { 34 | if (Utils.TryGetEnvironmentVariable(id, out string result)) 35 | return new UiDomString(result); 36 | return base.EvaluateIdentifierCore(id, root, depends_on); 37 | } 38 | 39 | protected override UiDomValue EvaluateApply(UiDomValue context, GudlExpression[] arglist, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 40 | { 41 | if (arglist.Length != 1) 42 | return UiDomUndefined.Instance; 43 | var expr = arglist[0]; 44 | UiDomValue right = context.Evaluate(expr, root, depends_on); 45 | if (right is UiDomString st) 46 | { 47 | return EvaluateIdentifier(st.Value, root, depends_on); 48 | } 49 | return UiDomUndefined.Instance; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomInt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Numerics; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | internal class UiDomInt : UiDomValue 8 | { 9 | public UiDomInt(BigInteger value) 10 | { 11 | Value = value; 12 | } 13 | 14 | public UiDomInt(int value) : this(new BigInteger(value)) { } 15 | 16 | public BigInteger Value { get; } 17 | 18 | public override bool Equals(object obj) 19 | { 20 | if (ReferenceEquals(this, obj)) 21 | return true; 22 | if (obj is UiDomInt i) 23 | return Value == i.Value; 24 | return false; 25 | } 26 | 27 | public override int GetHashCode() 28 | { 29 | return Value.GetHashCode() ^ typeof(UiDomInt).GetHashCode(); 30 | } 31 | 32 | public override bool ToBool() 33 | { 34 | return Value != 0; 35 | } 36 | 37 | public override bool TryToDouble(out double val) 38 | { 39 | try 40 | { 41 | val = (double)Value; 42 | return true; 43 | } 44 | catch (OverflowException) 45 | { 46 | val = 0.0; 47 | return false; 48 | } 49 | } 50 | 51 | public override bool TryToInt(out int val) 52 | { 53 | try 54 | { 55 | val = (int)Value; 56 | return true; 57 | } 58 | catch (OverflowException) 59 | { 60 | val = 0; 61 | return false; 62 | } 63 | } 64 | 65 | public override string ToString() 66 | { 67 | return Value.ToString(CultureInfo.InvariantCulture); 68 | } 69 | 70 | public override bool Compare(UiDomValue other, out int sign) 71 | { 72 | if (other is UiDomInt i) 73 | { 74 | if (Value == i.Value) 75 | sign = 0; 76 | else if (Value < i.Value) 77 | sign = -1; 78 | else 79 | sign = 1; 80 | return true; 81 | } 82 | if (other is UiDomDouble d) 83 | { 84 | // mixed comparison is complicated so don't duplicate it here 85 | var res = d.Compare(this, out sign); 86 | sign = -sign; 87 | return res; 88 | } 89 | return base.Compare(other, out sign); 90 | } 91 | 92 | public override bool ValueEquals(UiDomValue other) 93 | { 94 | if (Equals(other)) 95 | return true; 96 | if (other is UiDomDouble d) 97 | // mixed comparison is complicated so don't duplicate it here 98 | return d.Equals(this); 99 | return false; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomIsRelationship.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices; 3 | using Xalia.Gudl; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | internal class UiDomIsRelationship : UiDomValue 8 | { 9 | public UiDomIsRelationship(UiDomElement element, IsRelationshipType relationship_type) 10 | { 11 | Element = element; 12 | RelationshipType = relationship_type; 13 | } 14 | 15 | public UiDomElement Element { get; } 16 | public IsRelationshipType RelationshipType { get; } 17 | 18 | public enum IsRelationshipType 19 | { 20 | Child, 21 | Parent, 22 | Ancestor, 23 | Descendent, 24 | Sibling 25 | } 26 | 27 | public override string ToString() 28 | { 29 | switch (RelationshipType) 30 | { 31 | case IsRelationshipType.Child: 32 | return "is_child_of"; 33 | case IsRelationshipType.Parent: 34 | return "is_parent_of"; 35 | case IsRelationshipType.Ancestor: 36 | return "is_ancestor_of"; 37 | case IsRelationshipType.Descendent: 38 | return "is_descendent_of"; 39 | case IsRelationshipType.Sibling: 40 | return "is_sibling_of"; 41 | } 42 | return base.ToString(); 43 | } 44 | 45 | public override bool Equals(object obj) 46 | { 47 | if (obj is UiDomIsRelationship rel) 48 | { 49 | return rel.Element.Equals(Element) && rel.RelationshipType == RelationshipType; 50 | } 51 | return false; 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | return (Element, RelationshipType).GetHashCode() ^ typeof(UiDomIsRelationship).GetHashCode(); 57 | } 58 | 59 | protected override UiDomValue EvaluateApply(UiDomValue context, GudlExpression[] arglist, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 60 | { 61 | if (arglist.Length != 1) 62 | return UiDomUndefined.Instance; 63 | var expr = arglist[0]; 64 | var right_value = context.Evaluate(expr, root, depends_on); 65 | if (right_value is UiDomElement other && !(right_value is null)) 66 | { 67 | switch (RelationshipType) 68 | { 69 | case IsRelationshipType.Child: 70 | return UiDomBoolean.FromBool(other.Parent.Equals(Element)); 71 | case IsRelationshipType.Parent: 72 | return UiDomBoolean.FromBool(Element.Parent.Equals(other)); 73 | case IsRelationshipType.Ancestor: 74 | while (!(other.Parent is null)) 75 | { 76 | if (other.Parent.Equals(Element)) 77 | return UiDomBoolean.True; 78 | other = other.Parent; 79 | } 80 | return UiDomBoolean.False; 81 | case IsRelationshipType.Descendent: 82 | { 83 | var element = Element; 84 | while (!(element.Parent is null)) 85 | { 86 | if (element.Parent.Equals(other)) 87 | return UiDomBoolean.True; 88 | element = element.Parent; 89 | } 90 | return UiDomBoolean.False; 91 | } 92 | case IsRelationshipType.Sibling: 93 | { 94 | if (Element.Parent is null || other.Parent is null) 95 | { 96 | // root or defunct element 97 | return UiDomBoolean.FromBool(Element.Equals(other)); 98 | } 99 | else 100 | { 101 | return UiDomBoolean.FromBool(Element.Parent.Equals(other.Parent)); 102 | } 103 | } 104 | } 105 | } 106 | return UiDomUndefined.Instance; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices; 3 | using Xalia.Gudl; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | internal class UiDomMethod : UiDomValue 8 | { 9 | public UiDomElement Element { get; } 10 | public string Name { get; } 11 | public ApplyFn ApplyFunction { get; } 12 | 13 | public delegate UiDomValue ApplyFn(UiDomMethod method, UiDomValue context, GudlExpression[] arglist, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on); 14 | 15 | public UiDomMethod(UiDomElement element, string name, ApplyFn apply_function) 16 | { 17 | Element = element; 18 | Name = name; 19 | ApplyFunction = apply_function; 20 | } 21 | 22 | public UiDomMethod(string name, ApplyFn apply_function) : this(null, name, apply_function) 23 | { 24 | } 25 | 26 | protected override UiDomValue EvaluateApply(UiDomValue context, GudlExpression[] arglist, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 27 | { 28 | return ApplyFunction(this, context, arglist, root, depends_on); 29 | } 30 | 31 | public override bool Equals(object obj) 32 | { 33 | if (ReferenceEquals(this, obj)) 34 | return true; 35 | if (obj is UiDomMethod m) 36 | { 37 | return m.Element == Element && m.Name == Name; 38 | } 39 | return false; 40 | } 41 | 42 | public override int GetHashCode() 43 | { 44 | return typeof(UiDomMethod).GetHashCode() ^ (Element, Name).GetHashCode(); 45 | } 46 | 47 | public override string ToString() 48 | { 49 | if (Element is null) 50 | return Name; 51 | return $"{Element}.{Name}"; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomOnRelease.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xalia.Input; 3 | 4 | namespace Xalia.UiDom 5 | { 6 | internal class UiDomOnRelease : UiDomRoutine 7 | { 8 | public UiDomOnRelease(UiDomRoutine routine) : base("on_release", new UiDomValue[] { routine }) 9 | { 10 | Routine = routine; 11 | } 12 | 13 | public UiDomRoutine Routine { get; } 14 | 15 | public override async Task ProcessInputQueue(InputQueue queue) 16 | { 17 | InputState prev_state = new InputState(InputStateKind.Disconnected), state; 18 | bool held = false; 19 | do 20 | { 21 | state = await queue.Dequeue(); 22 | if (state.JustPressed(prev_state)) 23 | { 24 | held = true; 25 | } 26 | else if (held && !state.Pressed && state.Kind != InputStateKind.Disconnected) 27 | { 28 | held = false; 29 | Routine.Pulse(); 30 | } 31 | prev_state = state; 32 | } while (state.Kind != InputStateKind.Disconnected); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /xalia/UiDom/UiDomProviderBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Xalia.Gudl; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | internal class UiDomProviderBase : IUiDomProvider 8 | { 9 | public virtual void DumpProperties(UiDomElement element) 10 | { 11 | } 12 | 13 | public virtual UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 14 | { 15 | return UiDomUndefined.Instance; 16 | } 17 | 18 | public virtual UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 19 | { 20 | return UiDomUndefined.Instance; 21 | } 22 | 23 | public virtual Task<(bool, int, int)> GetClickablePointAsync(UiDomElement element) 24 | { 25 | return Task.FromResult((false, 0, 0)); 26 | } 27 | 28 | public virtual string[] GetTrackedProperties() 29 | { 30 | return null; 31 | } 32 | 33 | public virtual void NotifyElementRemoved(UiDomElement element) 34 | { 35 | } 36 | 37 | public virtual void TrackedPropertyChanged(UiDomElement element, string name, UiDomValue new_value) 38 | { 39 | } 40 | 41 | public virtual bool UnwatchProperty(UiDomElement element, GudlExpression expression) 42 | { 43 | return false; 44 | } 45 | 46 | public virtual bool WatchProperty(UiDomElement element, GudlExpression expression) 47 | { 48 | return false; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRadialDeadzone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Xalia.Gudl; 5 | using Xalia.Input; 6 | 7 | namespace Xalia.UiDom 8 | { 9 | internal class UiDomRadialDeadzone : UiDomRoutine 10 | { 11 | public UiDomRadialDeadzone(UiDomRoutine routine, double deadzone) : 12 | base("radial_deadzone", new UiDomValue[] { routine, new UiDomDouble(deadzone) }) 13 | { 14 | Routine = routine; 15 | Deadzone = deadzone; 16 | } 17 | 18 | public UiDomRoutine Routine { get; } 19 | public double Deadzone { get; } 20 | 21 | internal static UiDomValue ApplyFn(UiDomMethod method, UiDomValue context, GudlExpression[] arglist, UiDomRoot root, HashSet<(UiDomElement, GudlExpression)> depends_on) 22 | { 23 | if (arglist.Length < 2) 24 | return UiDomUndefined.Instance; 25 | 26 | var routine = context.Evaluate(arglist[0], root, depends_on) as UiDomRoutine; 27 | if (routine is null) 28 | return UiDomUndefined.Instance; 29 | 30 | if (!context.Evaluate(arglist[1], root, depends_on).TryToDouble(out var deadzone)) 31 | return UiDomUndefined.Instance; 32 | 33 | return new UiDomRadialDeadzone(routine, deadzone); 34 | } 35 | 36 | private static double ToEdgeDistance(short axis) 37 | { 38 | if (axis > 0) 39 | return 1.0-(axis / 32767.0); 40 | else 41 | return -1.0-(axis / 32768.0); 42 | } 43 | 44 | private static short FromEdgeDistance(double axis) 45 | { 46 | if (axis > 0.0) 47 | return (short)Math.Round((1.0 - axis) * 32767.0); 48 | else 49 | return (short)Math.Round((-1.0 - axis) * 32768.0); 50 | } 51 | 52 | public override async Task ProcessInputQueue(InputQueue queue) 53 | { 54 | var inner_queue = new InputQueue(); 55 | Utils.RunTask(Routine.ProcessInputQueue(inner_queue)); 56 | InputState state = default; 57 | 58 | do 59 | { 60 | state = await queue.Dequeue(); 61 | 62 | if (state.Kind is InputStateKind.AnalogJoystick) 63 | { 64 | double xedge = ToEdgeDistance(state.XAxis); 65 | double yedge = ToEdgeDistance(state.YAxis); 66 | double edge_distance = Math.Min(Math.Abs(xedge), Math.Abs(yedge)); 67 | double intensity = 1.0 - edge_distance; 68 | 69 | if (intensity <= Deadzone) 70 | { 71 | state.XAxis = 0; 72 | state.YAxis = 0; 73 | } 74 | else 75 | { 76 | double new_edge_distance = edge_distance / (1.0 - Deadzone); 77 | double multiplier = (1.0 - new_edge_distance) / (1.0 - edge_distance); 78 | state.XAxis = (short)Math.Round(state.XAxis * multiplier); 79 | state.YAxis = (short)Math.Round(state.YAxis * multiplier); 80 | } 81 | } 82 | else if (state.Kind is InputStateKind.AnalogButton) 83 | { 84 | double edge_distance = ToEdgeDistance(state.Intensity); 85 | double intensity = 1.0 - edge_distance; 86 | 87 | if (intensity <= Deadzone) 88 | { 89 | state.XAxis = 0; 90 | } 91 | else 92 | { 93 | state.XAxis = FromEdgeDistance(edge_distance / (1.0 - Deadzone)); 94 | } 95 | } 96 | inner_queue.Enqueue(state); 97 | } while (!(state.Kind is InputStateKind.Disconnected)); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRelationship.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices; 3 | using Xalia.Gudl; 4 | 5 | namespace Xalia.UiDom 6 | { 7 | public enum UiDomRelationshipKind 8 | { 9 | ThisOrAncestor, 10 | ThisOrDescendent, 11 | Ancestor, 12 | Descendent, 13 | Child, 14 | Parent, 15 | LastChild, 16 | NextSibling, 17 | PreviousSibling 18 | } 19 | 20 | public class UiDomRelationship : UiDomValue 21 | { 22 | public UiDomElement Element { get; } 23 | public UiDomRelationshipKind Kind { get; } 24 | 25 | static UiDomRelationship() 26 | { 27 | Names = new Dictionary(); 28 | Names["this_or_ancestor_matches"] = UiDomRelationshipKind.ThisOrAncestor; 29 | Names["this_or_descendent_matches"] = UiDomRelationshipKind.ThisOrDescendent; 30 | Names["ancestor_matches"] = UiDomRelationshipKind.Ancestor; 31 | Names["descendent_matches"] = UiDomRelationshipKind.Descendent; 32 | Names["child_matches"] = UiDomRelationshipKind.Child; 33 | Names["parent_matches"] = UiDomRelationshipKind.Parent; 34 | Names["last_child_matches"] = UiDomRelationshipKind.LastChild; 35 | Names["next_sibling_matches"] = UiDomRelationshipKind.NextSibling; 36 | Names["previous_sibling_matches"] = UiDomRelationshipKind.PreviousSibling; 37 | } 38 | 39 | public UiDomRelationship(UiDomElement element, UiDomRelationshipKind kind) 40 | { 41 | Element = element; 42 | Kind = kind; 43 | } 44 | 45 | public override bool Equals(object obj) 46 | { 47 | if (ReferenceEquals(this, obj)) 48 | return true; 49 | if (obj is UiDomRelationship rel) 50 | return Element.Equals(rel.Element) && Kind == rel.Kind; 51 | return false; 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | return (Element, Kind).GetHashCode() ^ typeof(UiDomRelationship).GetHashCode(); 57 | } 58 | 59 | public static Dictionary Names; 60 | 61 | public static string NameFromKind(UiDomRelationshipKind kind) 62 | { 63 | switch (kind) 64 | { 65 | case UiDomRelationshipKind.ThisOrAncestor: 66 | return "this_or_ancestor_matches"; 67 | case UiDomRelationshipKind.ThisOrDescendent: 68 | return "this_or_descendent_matches"; 69 | case UiDomRelationshipKind.Ancestor: 70 | return "ancestor_matches"; 71 | case UiDomRelationshipKind.Descendent: 72 | return "descendent_matches"; 73 | case UiDomRelationshipKind.Child: 74 | return "child_matches"; 75 | case UiDomRelationshipKind.Parent: 76 | return "parent_matches"; 77 | case UiDomRelationshipKind.LastChild: 78 | return "last_child_matches"; 79 | case UiDomRelationshipKind.NextSibling: 80 | return "next_sibling_matches"; 81 | case UiDomRelationshipKind.PreviousSibling: 82 | return "previous_sibling_matches"; 83 | default: 84 | return "unknown"; 85 | } 86 | } 87 | 88 | public string Name => NameFromKind(Kind); 89 | 90 | protected override UiDomValue EvaluateApply(UiDomValue context, GudlExpression[] arglist, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 91 | { 92 | if (arglist.Length != 1) 93 | return UiDomUndefined.Instance; 94 | var expr = arglist[0]; 95 | depends_on.Add((Element, new ApplyExpression( 96 | new IdentifierExpression(Name), 97 | new GudlExpression[] { expr }))); 98 | return Element.EvaluateRelationship(Kind, expr); 99 | } 100 | 101 | protected override UiDomValue EvaluateDot(UiDomValue context, GudlExpression expr, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 102 | { 103 | return EvaluateApply(context, new GudlExpression[] { expr }, root, depends_on); 104 | } 105 | 106 | public override string ToString() 107 | { 108 | return $"{Element}.{Name}"; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRoot.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xalia.Gudl; 3 | 4 | namespace Xalia.UiDom 5 | { 6 | public class UiDomRoot : UiDomElement 7 | { 8 | public UiDomRoot(GudlStatement[] rules, IUiDomApplication application) 9 | { 10 | Rules = GudlSelector.Flatten(rules).AsReadOnly(); 11 | Application = application; 12 | Application.RootElementCreated(this); 13 | } 14 | 15 | public IReadOnlyCollection<(GudlExpression, GudlDeclaration[])> Rules { get; } 16 | 17 | public IUiDomApplication Application { get; } 18 | 19 | public List GlobalProviders { get; private set; } = new List(); 20 | 21 | internal void RaiseElementDeclarationsChangedEvent(UiDomElement element) 22 | { 23 | Application.ElementDeclarationsChanged(element); 24 | } 25 | 26 | internal void RaiseElementDiedEvent(UiDomElement element) 27 | { 28 | Application.ElementDied(element); 29 | } 30 | 31 | public void AddGlobalProvider(IUiDomProvider provider, int index) 32 | { 33 | GlobalProviders.Insert(index, provider); 34 | AddedGlobalProvider(provider); 35 | } 36 | 37 | public void AddGlobalProvider(IUiDomProvider provider) 38 | { 39 | AddGlobalProvider(provider, GlobalProviders.Count); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRoutine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using Xalia.Input; 5 | 6 | namespace Xalia.UiDom 7 | { 8 | public abstract class UiDomRoutine : UiDomValue 9 | { 10 | public UiDomElement Element { get; } 11 | public string Name { get; } 12 | public UiDomValue[] Arglist { get; } 13 | 14 | public UiDomRoutine(UiDomElement element, string name, UiDomValue[] arglist) 15 | { 16 | Element = element; 17 | Name = name; 18 | Arglist = arglist; 19 | } 20 | 21 | public UiDomRoutine() : this(null, null, null) { } 22 | public UiDomRoutine(UiDomElement element) : this(element, null, null) { } 23 | public UiDomRoutine(string name) : this(null, name, null) { } 24 | public UiDomRoutine(UiDomElement element, string name) : this(element, name, null) { } 25 | public UiDomRoutine(string name, UiDomValue[] arglist) : this(null, name, arglist) { } 26 | 27 | public abstract Task ProcessInputQueue(InputQueue queue); 28 | 29 | public override string ToString() 30 | { 31 | var sb = new StringBuilder(); 32 | if (Element != null) 33 | { 34 | sb.Append(Element.ToString()); 35 | sb.Append('.'); 36 | } 37 | if (Name != null) 38 | { 39 | sb.Append(Name); 40 | } 41 | if (Arglist != null) 42 | { 43 | sb.Append('('); 44 | for (int i = 0; i < Arglist.Length; i++) 45 | { 46 | sb.Append(Arglist[i].ToString()); 47 | if (i != Arglist.Length - 1) 48 | sb.Append(", "); 49 | } 50 | sb.Append(')'); 51 | } 52 | return sb.ToString(); 53 | } 54 | 55 | public override bool Equals(object obj) 56 | { 57 | // Assumes that any 2 routines on the same element, name, and arguments are identical. 58 | // We should maintain that constraint for debugging purposes anyway. 59 | if (obj is UiDomRoutine rou) 60 | { 61 | if (Element != rou.Element || Name != rou.Name) 62 | return false; 63 | if ((Arglist is null) != (rou.Arglist is null)) 64 | return false; 65 | if (!(Arglist is null)) 66 | { 67 | if (Arglist.Length != rou.Arglist.Length) 68 | return false; 69 | for (int i = 0; i < Arglist.Length; i++) 70 | if (!Arglist[i].Equals(rou.Arglist[i])) 71 | return false; 72 | } 73 | return true; 74 | } 75 | return false; 76 | } 77 | 78 | public override int GetHashCode() 79 | { 80 | return (Element, Name, 81 | Arglist != null ? 0 : StructuralComparisons.StructuralEqualityComparer.GetHashCode(Arglist) 82 | ).GetHashCode() ^ typeof(UiDomRoutine).GetHashCode(); 83 | } 84 | 85 | public void Pulse() 86 | { 87 | var queue = new InputQueue(); 88 | queue.Enqueue(new InputState(InputStateKind.Pulse)); 89 | queue.Enqueue(new InputState(InputStateKind.Disconnected)); 90 | Utils.RunTask(ProcessInputQueue(queue)); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRoutineAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Xalia.UiDom 5 | { 6 | public class UiDomRoutineAsync : UiDomRoutinePress 7 | { 8 | public delegate Task AsyncRoutine(UiDomRoutineAsync obj); 9 | 10 | public AsyncRoutine Routine { get; } 11 | 12 | public UiDomRoutineAsync(UiDomElement element, string name, UiDomValue[] arglist, 13 | AsyncRoutine routine) : base(element, name, arglist) 14 | { 15 | Routine = routine; 16 | } 17 | 18 | public UiDomRoutineAsync(AsyncRoutine routine) : this(null, null, null, routine) { } 19 | public UiDomRoutineAsync(UiDomElement element, AsyncRoutine routine) : this(element, null, null, routine) { } 20 | public UiDomRoutineAsync(string name, AsyncRoutine routine) : this(null, name, null, routine) { } 21 | public UiDomRoutineAsync(UiDomElement element, string name, AsyncRoutine routine) : this(element, name, null, routine) { } 22 | public UiDomRoutineAsync(string name, UiDomValue[] arglist, AsyncRoutine routine) : this(null, name, arglist, routine) { } 23 | 24 | public override async Task OnPress() 25 | { 26 | try 27 | { 28 | await Routine(this); 29 | } 30 | catch (Exception e) 31 | { 32 | Utils.OnError(e); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRoutinePress.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xalia.Input; 3 | 4 | namespace Xalia.UiDom 5 | { 6 | public abstract class UiDomRoutinePress : UiDomRoutine 7 | { 8 | public UiDomRoutinePress() : base() { } 9 | public UiDomRoutinePress(UiDomElement element) : base(element) { } 10 | public UiDomRoutinePress(string name) : base(name) { } 11 | public UiDomRoutinePress(UiDomElement element, string name) : base(element, name) { } 12 | public UiDomRoutinePress(string name, UiDomValue[] arglist) : base(name, arglist) { } 13 | public UiDomRoutinePress(UiDomElement element, string name, UiDomValue[] arglist) : base(element, name, arglist) { } 14 | 15 | public abstract Task OnPress(); 16 | 17 | public override async Task ProcessInputQueue(InputQueue queue) 18 | { 19 | InputState prev_state = new InputState(InputStateKind.Disconnected), state; 20 | do 21 | { 22 | state = await queue.Dequeue(); 23 | if (state.JustPressed(prev_state)) 24 | { 25 | await OnPress(); 26 | } 27 | prev_state = state; 28 | } while (state.Kind != InputStateKind.Disconnected); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRoutineSequence.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xalia.Input; 3 | 4 | namespace Xalia.UiDom 5 | { 6 | internal class UiDomRoutineSequence : UiDomRoutine 7 | { 8 | public UiDomRoutineSequence(UiDomRoutine first, UiDomRoutine second) 9 | { 10 | First = first; 11 | Second = second; 12 | } 13 | 14 | public UiDomRoutine First { get; } 15 | public UiDomRoutine Second { get; } 16 | 17 | public override bool Equals(object obj) 18 | { 19 | if (obj is UiDomRoutineSequence seq) 20 | { 21 | return First.Equals(seq.First) && Second.Equals(seq.Second); 22 | } 23 | return false; 24 | } 25 | 26 | public override int GetHashCode() 27 | { 28 | return (First, Second).GetHashCode() ^ typeof(UiDomRoutineSequence).GetHashCode(); 29 | } 30 | 31 | public override string ToString() 32 | { 33 | return $"{First}+{Second}"; 34 | } 35 | 36 | public override async Task ProcessInputQueue(InputQueue queue) 37 | { 38 | InputQueue queue1 = new InputQueue(); 39 | Utils.RunTask(First.ProcessInputQueue(queue1)); 40 | InputQueue queue2 = new InputQueue(); 41 | Utils.RunTask(Second.ProcessInputQueue(queue2)); 42 | InputState state; 43 | do 44 | { 45 | state = await queue.Dequeue(); 46 | 47 | queue1.Enqueue(state); 48 | if (state.Kind != InputStateKind.Disconnected) 49 | await queue1.WaitForConsumer(); 50 | 51 | queue2.Enqueue(state); 52 | if (state.Kind != InputStateKind.Disconnected) 53 | await queue2.WaitForConsumer(); 54 | } while (state.Kind != InputStateKind.Disconnected); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomRoutineSync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Xalia.UiDom 5 | { 6 | public class UiDomRoutineSync : UiDomRoutinePress 7 | { 8 | public Action Routine { get; } 9 | 10 | public UiDomRoutineSync(UiDomElement element, string name, UiDomValue[] arglist, 11 | Action routine) : base(element, name, arglist) 12 | { 13 | Routine = routine; 14 | } 15 | 16 | public UiDomRoutineSync(Action routine) : this(null, null, null, routine) { } 17 | public UiDomRoutineSync(UiDomElement element, Action routine) : this(element, null, null, routine) { } 18 | public UiDomRoutineSync(string name, Action routine) : this(null, name, null, routine) { } 19 | public UiDomRoutineSync(UiDomElement element, string name, Action routine) : this(element, name, null, routine) { } 20 | public UiDomRoutineSync(string name, UiDomValue[] arglist, Action routine) : this(null, name, arglist, routine) { } 21 | 22 | public override Task OnPress() 23 | { 24 | try 25 | { 26 | Routine(this); 27 | } 28 | catch (Exception e) 29 | { 30 | Utils.OnError(e); 31 | } 32 | return Task.CompletedTask; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomString.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.UiDom 2 | { 3 | public class UiDomString : UiDomValue 4 | { 5 | public UiDomString(string value) 6 | { 7 | Value = value; 8 | } 9 | 10 | public override string ToString() 11 | { 12 | // FIXME: Escape string if necessary 13 | return $"\"{Value}\""; 14 | } 15 | public string Value { get; } 16 | 17 | public override bool ToBool() 18 | { 19 | return Value != string.Empty; 20 | } 21 | 22 | public override bool Equals(object obj) 23 | { 24 | if (ReferenceEquals(this, obj)) 25 | return true; 26 | if (obj is UiDomString st) 27 | return Value == st.Value; 28 | return false; 29 | } 30 | 31 | public override int GetHashCode() 32 | { 33 | return Value.GetHashCode() ^ typeof(UiDomString).GetHashCode(); 34 | } 35 | 36 | public override bool Compare(UiDomValue other, out int sign) 37 | { 38 | if (other is UiDomString s) 39 | { 40 | sign = string.CompareOrdinal(Value, s.Value); 41 | return true; 42 | } 43 | return base.Compare(other, out sign); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /xalia/UiDom/UiDomUndefined.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia.UiDom 2 | { 3 | public class UiDomUndefined : UiDomValue 4 | { 5 | private UiDomUndefined() 6 | { 7 | 8 | } 9 | 10 | public static UiDomUndefined Instance = new UiDomUndefined(); 11 | 12 | public override string ToString() 13 | { 14 | return "undefined"; 15 | } 16 | 17 | public override bool ToBool() 18 | { 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /xalia/Util/RangeList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | 6 | namespace Xalia.Util 7 | { 8 | public class RangeList : IList 9 | { 10 | public RangeList(int start, int end) 11 | { 12 | if (end < start) 13 | throw new ArgumentException($"end ({end.ToString(CultureInfo.InvariantCulture)}) must be greater than or equal to start ({start.ToString(CultureInfo.InvariantCulture)})"); 14 | Start = start; 15 | End = end; 16 | } 17 | 18 | public int this[int index] { get => Start + index; } 19 | int IList.this[int index] { get => this[index]; set => throw new NotImplementedException(); } 20 | 21 | public int Start { get; } 22 | public int End { get; } 23 | 24 | public int Count => End - Start; 25 | 26 | public bool IsReadOnly => true; 27 | 28 | public void Add(int item) 29 | { 30 | throw new System.NotImplementedException(); 31 | } 32 | 33 | public void Clear() 34 | { 35 | throw new System.NotImplementedException(); 36 | } 37 | 38 | public bool Contains(int item) 39 | { 40 | return Start <= item && item < End; 41 | } 42 | 43 | public void CopyTo(int[] array, int arrayIndex) 44 | { 45 | var count = Count; 46 | for (int i=0; i < count; i++) 47 | { 48 | array[arrayIndex + i] = Start + i; 49 | } 50 | } 51 | 52 | public IEnumerator GetEnumerator() 53 | { 54 | return new RangeEnumerator(this); 55 | } 56 | 57 | public int IndexOf(int item) 58 | { 59 | if (Contains(item)) 60 | return item - Start; 61 | return -1; 62 | } 63 | 64 | public void Insert(int index, int item) 65 | { 66 | throw new System.NotImplementedException(); 67 | } 68 | 69 | public bool Remove(int item) 70 | { 71 | throw new System.NotImplementedException(); 72 | } 73 | 74 | public void RemoveAt(int index) 75 | { 76 | throw new System.NotImplementedException(); 77 | } 78 | 79 | IEnumerator IEnumerable.GetEnumerator() 80 | { 81 | return GetEnumerator(); 82 | } 83 | 84 | public class RangeEnumerator : IEnumerator 85 | { 86 | public RangeEnumerator(RangeList range) 87 | { 88 | Range = range; 89 | Reset(); 90 | } 91 | 92 | public RangeList Range { get; } 93 | 94 | public int Current { get; set; } 95 | 96 | object IEnumerator.Current => Current; 97 | 98 | public void Dispose() 99 | { 100 | } 101 | 102 | public bool MoveNext() 103 | { 104 | Current++; 105 | return Current < Range.End; 106 | } 107 | 108 | public void Reset() 109 | { 110 | Current = Range.Start - 1; 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /xalia/Utils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xalia.Interop; 8 | 9 | #if WINDOWS 10 | using static Xalia.Interop.Win32; 11 | #endif 12 | 13 | namespace Xalia 14 | { 15 | internal static class Utils 16 | { 17 | static async Task DoRunTask(object o) 18 | { 19 | try 20 | { 21 | Task t = (Task)o; 22 | await t; 23 | } 24 | catch (Exception e) 25 | { 26 | OnError(e); 27 | } 28 | } 29 | 30 | static void RunTaskCallback(object o) 31 | { 32 | _ = DoRunTask(o); 33 | } 34 | 35 | internal static void RunTask(Task t) 36 | { 37 | SynchronizationContext.Current.Send(RunTaskCallback, t); 38 | } 39 | 40 | #if WINDOWS 41 | private static void WineDebugWriteLine(string str) 42 | { 43 | var bytes = Encoding.UTF8.GetBytes(str + "\n\0"); 44 | 45 | while (bytes.Length > 1018) 46 | { 47 | // Line is too long for Wine to output, break it up. 48 | string substr = str.Substring(0, 300); 49 | 50 | if (substr.Contains("\n")) 51 | { 52 | int index = substr.LastIndexOf('\n'); 53 | substr = str.Substring(0, index); 54 | str = str.Substring(index + 1); 55 | } 56 | else 57 | { 58 | str = str.Substring(300); 59 | } 60 | 61 | bytes = Encoding.UTF8.GetBytes(substr + "\n\0"); 62 | __wine_dbg_output(bytes); 63 | 64 | bytes = Encoding.UTF8.GetBytes(str + "\n\0"); 65 | } 66 | 67 | __wine_dbg_output(bytes); 68 | } 69 | 70 | static bool useWineDebug = true; 71 | #endif 72 | 73 | internal static void DebugWriteLine(string str) 74 | { 75 | #if WINDOWS 76 | if (IsWindows() && useWineDebug) 77 | { 78 | try 79 | { 80 | WineDebugWriteLine(str); 81 | return; 82 | } 83 | catch (EntryPointNotFoundException) 84 | { 85 | useWineDebug = false; 86 | } 87 | } 88 | #endif 89 | Console.Error.WriteLine(str); 90 | } 91 | 92 | internal static void DebugWriteLine(object obj) 93 | { 94 | DebugWriteLine(obj.ToString()); 95 | } 96 | 97 | internal static void OnError(Exception obj) 98 | { 99 | DebugWriteLine("Unhandled exception in Xalia:"); 100 | DebugWriteLine(obj); 101 | #if DEBUG 102 | Environment.FailFast(obj.ToString()); 103 | #endif 104 | } 105 | 106 | static void RunActionCallback(object o) 107 | { 108 | try 109 | { 110 | ((Action)o).Invoke(); 111 | } 112 | catch (Exception e) 113 | { 114 | OnError(e); 115 | } 116 | } 117 | 118 | internal static void RunIdle(Action action) 119 | { 120 | SynchronizationContext.Current.Post(RunActionCallback, action); 121 | } 122 | 123 | internal static Task WaitAsync(WaitHandle handle) 124 | { 125 | var result = new TaskCompletionSource(); 126 | 127 | ThreadPool.RegisterWaitForSingleObject(handle, (object state, bool timedOut) => 128 | { 129 | result.SetResult(timedOut); 130 | }, null, -1, true); 131 | 132 | return result.Task; 133 | } 134 | 135 | internal static Task WaitAsync(SafeWaitHandle handle, bool ownHandle) 136 | { 137 | return WaitAsync(new Win32WaitHandle(handle, ownHandle)); 138 | } 139 | 140 | internal static Task WaitAsync(SafeWaitHandle handle) 141 | { 142 | return WaitAsync(handle, false); 143 | } 144 | 145 | internal static bool TryGetEnvironmentVariable(string name, out string result) 146 | { 147 | result = Environment.GetEnvironmentVariable(name); 148 | return !(result is null); 149 | } 150 | internal static bool IsUnix() 151 | { 152 | int p = (int)Environment.OSVersion.Platform; 153 | // Intentionally excluding macOS from this check as AT-SPI is not standard there 154 | return p == 4 || p == 128; 155 | } 156 | 157 | internal static bool IsWindows() 158 | { 159 | return Environment.OSVersion.Platform == PlatformID.Win32NT; 160 | } 161 | 162 | internal static bool DictionariesEqual(Dictionary a, Dictionary b) 163 | { 164 | if (a.Count != b.Count) 165 | return false; 166 | foreach (var kvp in a) 167 | { 168 | if (!b.TryGetValue(kvp.Key, out var value) || kvp.Value != value) 169 | return false; 170 | } 171 | return true; 172 | } 173 | 174 | internal static int TruncatePtr(IntPtr i) 175 | { 176 | return unchecked((int)i.ToInt64()); 177 | } 178 | 179 | internal static void UnhandledException(object sender, UnhandledExceptionEventArgs e) 180 | { 181 | OnError((Exception)e.ExceptionObject); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /xalia/Win32/ElementIdentifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static Xalia.Interop.Win32; 3 | 4 | namespace Xalia.Win32 5 | { 6 | /* Represents a reference to a child element, generally so that a UIDomElement can be constructed. */ 7 | internal struct ElementIdentifier 8 | { 9 | /* Win32: */ 10 | public IntPtr root_hwnd; 11 | public bool is_root_hwnd; 12 | /* MSAA: */ 13 | public IAccessible acc; 14 | public IntPtr punk; /* acc as unmanaged pointer */ 15 | public IAccessible2 acc2; 16 | public int acc2_uniqueId; 17 | public int child_id; 18 | /* UIA: */ 19 | public IRawElementProviderSimple prov; 20 | public int[] runtime_id; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /xalia/Win32/HwndDialogProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Threading.Tasks; 5 | using Xalia.Gudl; 6 | using Xalia.UiDom; 7 | using static Xalia.Interop.Win32; 8 | 9 | namespace Xalia.Win32 10 | { 11 | internal class HwndDialogProvider : UiDomProviderBase 12 | { 13 | public HwndDialogProvider(HwndProvider hwndProvider) 14 | { 15 | HwndProvider = hwndProvider; 16 | } 17 | 18 | public HwndProvider HwndProvider { get; } 19 | 20 | public int DefId { get; private set; } 21 | public bool DefIdKnown { get; private set; } 22 | private bool _fetchingDefId; 23 | 24 | public override void DumpProperties(UiDomElement element) 25 | { 26 | if (DefIdKnown) 27 | Utils.DebugWriteLine($" win32_dialog_defid: {DefId}"); 28 | } 29 | 30 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 31 | { 32 | switch (identifier) 33 | { 34 | case "is_win32_dialog": 35 | case "is_hwnd_dialog": 36 | return UiDomBoolean.True; 37 | case "win32_dialog_defid": 38 | depends_on.Add((element, new IdentifierExpression("win32_dialog_defid"))); 39 | if (DefIdKnown) 40 | return new UiDomInt(DefId); 41 | return UiDomUndefined.Instance; 42 | } 43 | return UiDomUndefined.Instance; 44 | } 45 | 46 | static readonly UiDomValue role = new UiDomEnum(new string[] { "dialog" }); 47 | 48 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 49 | { 50 | switch (identifier) 51 | { 52 | case "role": 53 | case "control_type": 54 | return role; 55 | case "dialog": 56 | return UiDomBoolean.True; 57 | case "defid": 58 | return element.EvaluateIdentifier("win32_dialog_defid", element.Root, depends_on); 59 | } 60 | return UiDomUndefined.Instance; 61 | } 62 | 63 | public override bool WatchProperty(UiDomElement element, GudlExpression expression) 64 | { 65 | if (expression is IdentifierExpression id) 66 | { 67 | switch (id.Name) 68 | { 69 | case "win32_dialog_defid": 70 | if (!_fetchingDefId) 71 | { 72 | _fetchingDefId = true; 73 | Utils.RunTask(FetchDefId()); 74 | } 75 | return true; 76 | } 77 | } 78 | return false; 79 | } 80 | 81 | private async Task FetchDefId() 82 | { 83 | int result; 84 | try 85 | { 86 | result = (int)await SendMessageAsync(HwndProvider.Hwnd, DM_GETDEFID, IntPtr.Zero, IntPtr.Zero); 87 | } 88 | catch (Win32Exception e) 89 | { 90 | if (!HwndProvider.IsExpectedException(e)) 91 | throw; 92 | return; 93 | } 94 | if (HIWORD(result) == DC_HASDEFID) 95 | { 96 | DefId = LOWORD(result); 97 | DefIdKnown = true; 98 | HwndProvider.Element.PropertyChanged("win32_dialog_defid", DefId); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /xalia/Win32/HwndEditProvider.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.Collections.Generic; 4 | using Xalia.Gudl; 5 | using Xalia.UiDom; 6 | 7 | using static Xalia.Interop.Win32; 8 | 9 | namespace Xalia.Win32 10 | { 11 | internal class HwndEditProvider : UiDomProviderBase, IWin32Styles 12 | { 13 | public HwndEditProvider(HwndProvider hwndProvider) 14 | { 15 | HwndProvider = hwndProvider; 16 | } 17 | public HwndProvider HwndProvider { get; } 18 | 19 | public IntPtr Hwnd => HwndProvider.Hwnd; 20 | public Win32Connection Connection => HwndProvider.Connection; 21 | public UiDomElement Element => HwndProvider.Element; 22 | public int Tid => HwndProvider.Tid; 23 | 24 | public CommandThread CommandThread => HwndProvider.CommandThread; 25 | 26 | static UiDomEnum role = new UiDomEnum(new string[] { "edit", "text_box", "textbox" }); 27 | 28 | static string[] style_names = 29 | { 30 | "center", 31 | "right", 32 | "multiline", 33 | "uppercase", 34 | "lowercase", 35 | "password", 36 | "autovscroll", 37 | "autohscroll", 38 | "nohidesel", 39 | "combo", 40 | "oemconvert", 41 | "readonly", 42 | "wantreturn", 43 | "number" 44 | }; 45 | 46 | static Dictionary style_flags; 47 | 48 | static HwndEditProvider() 49 | { 50 | style_flags = new Dictionary(); 51 | for (int i=0; i depends_on) 60 | { 61 | switch (identifier) 62 | { 63 | case "is_hwnd_edit": 64 | case "is_hwndedit": 65 | return UiDomBoolean.True; 66 | case "role": 67 | case "control_type": 68 | return role; 69 | } 70 | return base.EvaluateIdentifier(element, identifier, depends_on); 71 | } 72 | 73 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 74 | { 75 | switch (identifier) 76 | { 77 | case "edit": 78 | case "text_box": 79 | case "textbox": 80 | return UiDomBoolean.True; 81 | case "left": 82 | depends_on.Add((element, new IdentifierExpression("win32_style"))); 83 | return UiDomBoolean.FromBool((HwndProvider.Style & (ES_CENTER | ES_RIGHT)) == 0); 84 | } 85 | if (style_flags.TryGetValue(identifier, out var flag)) 86 | { 87 | depends_on.Add((element, new IdentifierExpression("win32_style"))); 88 | return UiDomBoolean.FromBool((HwndProvider.Style & flag) != 0); 89 | } 90 | return base.EvaluateIdentifierLate(element, identifier, depends_on); 91 | } 92 | 93 | public void GetStyleNames(int style, List names) 94 | { 95 | if ((style & (ES_CENTER | ES_RIGHT)) == 0) 96 | names.Add("left"); 97 | for (int i=0; i GetMinimumIncrementAsync(UiDomElement element) 21 | { 22 | return Task.FromResult(1.0); 23 | } 24 | 25 | double remainder; 26 | 27 | public async Task OffsetValueAsync(UiDomElement element, double offset) 28 | { 29 | bool result = true; 30 | offset += remainder; 31 | if (offset <= -1 || offset >= 1) 32 | { 33 | var view_info = await Parent.GetViewInfoAsync(); 34 | var int_offset = (int)Math.Truncate(offset); 35 | 36 | var new_index = view_info.top_index + int_offset; 37 | 38 | if (new_index < 0) 39 | new_index = 0; 40 | else if (new_index >= view_info.item_count) 41 | new_index = view_info.item_count - 1; 42 | 43 | if (new_index != view_info.top_index) 44 | await SendMessageAsync(Parent.Hwnd, LB_SETTOPINDEX, new IntPtr(new_index), IntPtr.Zero); 45 | 46 | offset -= int_offset; 47 | } 48 | remainder = offset; 49 | return result; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /xalia/Win32/HwndListViewScrollProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xalia.UiDom; 4 | using static Xalia.Interop.Win32; 5 | 6 | namespace Xalia.Win32 7 | { 8 | internal class HwndListViewScrollProvider : UiDomProviderBase, IUiDomValueProvider 9 | { 10 | public HwndListViewScrollProvider(HwndListViewProvider parent, NonclientScrollProvider scroll) 11 | { 12 | Parent = parent; 13 | Scroll = scroll; 14 | } 15 | 16 | public HwndListViewProvider Parent { get; } 17 | public NonclientScrollProvider Scroll { get; } 18 | 19 | public bool Vertical => Scroll.Vertical; 20 | public IntPtr Hwnd => Parent.Hwnd; 21 | 22 | public async Task GetMinimumIncrementAsync(UiDomElement element) 23 | { 24 | switch (await Parent.GetViewAsync()) 25 | { 26 | case LV_VIEW_DETAILS: 27 | if (Vertical) 28 | // One item should always be reasonable 29 | return 1.0; 30 | break; 31 | case LV_VIEW_LIST: 32 | if (!Vertical) 33 | // One column should always be reasonable 34 | return 1.0; 35 | break; 36 | } 37 | return 0.0; // fallback on default scrollbar implementation 38 | } 39 | 40 | double remainder; 41 | 42 | public async Task OffsetValueAsync(UiDomElement element, double offset) 43 | { 44 | bool result = true; 45 | offset += remainder; 46 | if (offset <= -1 || offset >= 1) 47 | { 48 | var int_offset = (int)Math.Round(offset); 49 | var remote_int_offset = int_offset; 50 | 51 | var view = await Parent.GetViewAsync(); 52 | 53 | switch (view) 54 | { 55 | case LV_VIEW_DETAILS: 56 | if (Vertical) 57 | { 58 | // Can only scroll vertically in item increments. LVM_SCROLL expects 59 | // pixels, but the win32 scroll info is by item index. 60 | var bounds = await Parent.GetItemRectAsync(0, LVIR_SELECTBOUNDS); 61 | remote_int_offset *= bounds.height; 62 | } 63 | break; 64 | case LV_VIEW_LIST: 65 | if (!Vertical) 66 | { 67 | // Can only scroll in column increments. LVM_SCROLL expects 68 | // pixels, but the win32 scroll info is by column. 69 | var columnwidth = Utils.TruncatePtr( 70 | await SendMessageAsync(Hwnd, LVM_GETCOLUMNWIDTH, IntPtr.Zero, IntPtr.Zero)); 71 | remote_int_offset *= columnwidth; 72 | } 73 | break; 74 | } 75 | 76 | if (Vertical) 77 | result = await SendMessageAsync(Hwnd, LVM_SCROLL, IntPtr.Zero, new IntPtr(remote_int_offset)) != IntPtr.Zero; 78 | else 79 | result = await SendMessageAsync(Hwnd, LVM_SCROLL, new IntPtr(remote_int_offset), IntPtr.Zero) != IntPtr.Zero; 80 | 81 | offset -= int_offset; 82 | } 83 | remainder = offset; 84 | return result; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /xalia/Win32/HwndMsaaChildProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Xalia.Gudl; 4 | using Xalia.UiDom; 5 | 6 | namespace Xalia.Win32 7 | { 8 | internal class HwndMsaaChildProvider : UiDomProviderBase 9 | { 10 | internal HwndMsaaChildProvider(UiDomElement element, HwndProvider hwndRoot, int childId) : base() 11 | { 12 | Element = element; 13 | HwndRoot = hwndRoot; 14 | ChildId = childId; 15 | 16 | // TODO: If HwndRoot has an AccessibleProvider, use it to get an AccessibleProvider 17 | // for this child. 18 | } 19 | 20 | public UiDomElement Element { get; } 21 | public HwndProvider HwndRoot { get; } 22 | public int ChildId { get; } 23 | 24 | public override void DumpProperties(UiDomElement element) 25 | { 26 | Utils.DebugWriteLine($" msaa_child_id: {ChildId}"); 27 | HwndRoot.ChildDumpProperties(); 28 | base.DumpProperties(element); 29 | } 30 | 31 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 32 | { 33 | if (identifier == "msaa_child_id") 34 | return new UiDomInt(ChildId); 35 | return HwndRoot.ChildEvaluateIdentifier(identifier, depends_on); 36 | } 37 | 38 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 39 | { 40 | if (identifier == "child_id") 41 | return new UiDomInt(ChildId); 42 | return HwndRoot.ChildEvaluateIdentifierLate(identifier, depends_on); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /xalia/Win32/HwndRichEditProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Xalia.Gudl; 4 | using Xalia.UiDom; 5 | 6 | namespace Xalia.Win32 7 | { 8 | internal class HwndRichEditProvider : HwndEditProvider 9 | { 10 | public HwndRichEditProvider(HwndProvider hwndProvider) : base(hwndProvider) 11 | { 12 | } 13 | 14 | static UiDomEnum role = new UiDomEnum(new string[] { "document" }); 15 | 16 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 17 | { 18 | switch (identifier) 19 | { 20 | case "is_hwnd_richedit": 21 | case "is_hwnd_rich_edit": 22 | return UiDomBoolean.True; 23 | case "role": 24 | case "control_type": 25 | return role; 26 | } 27 | return base.EvaluateIdentifier(element, identifier, depends_on); 28 | } 29 | 30 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 31 | { 32 | switch (identifier) 33 | { 34 | case "document": 35 | return UiDomBoolean.True; 36 | } 37 | return base.EvaluateIdentifierLate(element, identifier, depends_on); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /xalia/Win32/HwndSysLinkProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Xalia.Gudl; 4 | using Xalia.UiDom; 5 | 6 | namespace Xalia.Win32 7 | { 8 | internal class HwndSysLinkProvider : UiDomProviderBase, IWin32Styles, IWin32NameChange 9 | { 10 | public HwndSysLinkProvider(HwndProvider hwndProvider) 11 | { 12 | HwndProvider = hwndProvider; 13 | } 14 | 15 | public HwndProvider HwndProvider { get; } 16 | public IntPtr Hwnd => HwndProvider.Hwnd; 17 | public UiDomElement Element => HwndProvider.Element; 18 | public Win32Connection Connection => HwndProvider.Connection; 19 | public UiDomRoot Root => Element.Root; 20 | public int Pid => HwndProvider.Pid; 21 | 22 | static readonly UiDomEnum role = new UiDomEnum(new[] { "sys_link", "syslink", "html_container", "htmlcontainer" }); 23 | 24 | static string[] style_names = 25 | { 26 | "transparent", 27 | "ignorereturn", 28 | "noprefix", 29 | "usevisualstyle", 30 | "usecustomtext", 31 | "right" 32 | }; 33 | 34 | static Dictionary style_flags; 35 | 36 | static HwndSysLinkProvider() 37 | { 38 | style_flags = new Dictionary(); 39 | for (int i=0; i depends_on) 48 | { 49 | switch (identifier) 50 | { 51 | case "is_hwnd_sys_link": 52 | case "is_hwnd_syslink": 53 | return UiDomBoolean.True; 54 | } 55 | 56 | return UiDomUndefined.Instance; 57 | } 58 | 59 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 60 | { 61 | switch (identifier) 62 | { 63 | case "sys_link": 64 | case "syslink": 65 | case "html_container": 66 | case "htmlcontainer": 67 | return UiDomBoolean.True; 68 | case "role": 69 | case "control_type": 70 | return role; 71 | } 72 | if (style_flags.TryGetValue(identifier, out var flag)) 73 | { 74 | depends_on.Add((element, new IdentifierExpression("win32_style"))); 75 | return UiDomBoolean.FromBool((HwndProvider.Style & flag) != 0); 76 | } 77 | return base.EvaluateIdentifierLate(element, identifier, depends_on); 78 | } 79 | 80 | public void GetStyleNames(int style, List names) 81 | { 82 | for (int i=0; i()?.MsaaChildrenReordered(); 94 | Win32Connection.RecursiveLocationChange(Element); 95 | foreach (var child in Element.Children) 96 | { 97 | child.ProviderByType()?.MsaaNameChange(); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /xalia/Win32/HwndTabItemProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Xalia.Gudl; 4 | using Xalia.UiDom; 5 | 6 | namespace Xalia.Win32 7 | { 8 | internal class HwndTabItemProvider : UiDomProviderBase 9 | { 10 | public HwndTabItemProvider(HwndTabProvider parent, UiDomElement element) 11 | { 12 | Parent = parent; 13 | Element = element; 14 | } 15 | 16 | public HwndTabProvider Parent { get; } 17 | public UiDomElement Element { get; } 18 | 19 | public int ChildId 20 | { 21 | get 22 | { 23 | return Element.IndexInParent + 1; 24 | } 25 | } 26 | 27 | public override void DumpProperties(UiDomElement element) 28 | { 29 | if (Parent.SelectionIndexKnown) 30 | Utils.DebugWriteLine($" selected: {Parent.SelectionIndex == ChildId - 1}"); 31 | if (Parent.ItemRectsKnown && ChildId <= Parent.ItemRects.Length) 32 | { 33 | Utils.DebugWriteLine($" rect: {new Win32Rect(Parent.ItemRects[ChildId - 1])}"); 34 | } 35 | base.DumpProperties(element); 36 | } 37 | 38 | static readonly UiDomEnum role = new UiDomEnum(new[] { "tab_item", "tabitem", "page_tab", "pagetab" }); 39 | 40 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 41 | { 42 | switch (identifier) 43 | { 44 | case "is_hwnd_tab_item": 45 | case "is_hwnd_subelement": 46 | return UiDomBoolean.True; 47 | } 48 | return base.EvaluateIdentifier(element, identifier, depends_on); 49 | } 50 | 51 | public override UiDomValue EvaluateIdentifierLate(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 52 | { 53 | switch (identifier) 54 | { 55 | case "selected": 56 | depends_on.Add((Parent.Element, new IdentifierExpression("win32_selection_index"))); 57 | if (Parent.SelectionIndexKnown) 58 | return UiDomBoolean.FromBool(Parent.SelectionIndex == ChildId - 1); 59 | break; 60 | case "x": 61 | { 62 | depends_on.Add((Parent.Element, new IdentifierExpression("win32_pos"))); 63 | var rects = Parent.GetItemRects(depends_on); 64 | if (!(rects is null) && ChildId <= rects.Length && Parent.HwndProvider.WindowRectsKnown) 65 | return new UiDomInt(Parent.HwndProvider.X + rects[ChildId - 1].left); 66 | break; 67 | } 68 | case "y": 69 | { 70 | depends_on.Add((Parent.Element, new IdentifierExpression("win32_pos"))); 71 | var rects = Parent.GetItemRects(depends_on); 72 | if (!(rects is null) && ChildId <= rects.Length && Parent.HwndProvider.WindowRectsKnown) 73 | return new UiDomInt(Parent.HwndProvider.Y + rects[ChildId - 1].top); 74 | break; 75 | } 76 | case "width": 77 | { 78 | var rects = Parent.GetItemRects(depends_on); 79 | if (!(rects is null) && ChildId <= rects.Length) 80 | return new UiDomInt(rects[ChildId - 1].right - rects[ChildId - 1].left); 81 | break; 82 | } 83 | case "height": 84 | { 85 | var rects = Parent.GetItemRects(depends_on); 86 | if (!(rects is null) && ChildId <= rects.Length) 87 | return new UiDomInt(rects[ChildId - 1].bottom - rects[ChildId - 1].top); 88 | break; 89 | } 90 | case "rect": 91 | { 92 | var rects = Parent.GetItemRects(depends_on); 93 | if (!(rects is null) && ChildId <= rects.Length) 94 | return new Win32Rect(rects[ChildId - 1]); 95 | break; 96 | } 97 | case "tab_item": 98 | case "tabitem": 99 | case "page_tab": 100 | case "pagetab": 101 | case "enabled": 102 | case "visible": 103 | return UiDomBoolean.True; 104 | case "role": 105 | case "control_type": 106 | return role; 107 | } 108 | return base.EvaluateIdentifierLate(element, identifier, depends_on); 109 | } 110 | 111 | public override async Task<(bool, int, int)> GetClickablePointAsync(UiDomElement element) 112 | { 113 | var rects = Parent.GetItemRects(new HashSet<(UiDomElement, GudlExpression)>()); 114 | 115 | if (rects is null) 116 | { 117 | await Parent.FetchItemRects(); 118 | rects = Parent.GetItemRects(new HashSet<(UiDomElement, GudlExpression)>()); 119 | } 120 | 121 | if (!(rects is null) && ChildId <= rects.Length) 122 | { 123 | var rect = rects[ChildId - 1]; 124 | return (true, rect.left + rect.width / 2, rect.top + rect.height / 2); 125 | } 126 | 127 | return await base.GetClickablePointAsync(element); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /xalia/Win32/IWin32Container.cs: -------------------------------------------------------------------------------- 1 | using Xalia.UiDom; 2 | 3 | namespace Xalia.Win32 4 | { 5 | internal interface IWin32Container : IUiDomProvider 6 | { 7 | void MsaaChildCreated(int ChildId); 8 | void MsaaChildDestroyed(int ChildId); 9 | void MsaaChildrenReordered(); 10 | 11 | UiDomElement GetMsaaChild(int ChildId); 12 | } 13 | } -------------------------------------------------------------------------------- /xalia/Win32/IWin32LocationChange.cs: -------------------------------------------------------------------------------- 1 | using Xalia.UiDom; 2 | 3 | namespace Xalia.Win32 4 | { 5 | internal interface IWin32LocationChange : IUiDomProvider 6 | { 7 | void MsaaLocationChange(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /xalia/Win32/IWin32NameChange.cs: -------------------------------------------------------------------------------- 1 | using Xalia.UiDom; 2 | 3 | namespace Xalia.Win32 4 | { 5 | interface IWin32NameChange : IUiDomProvider 6 | { 7 | void MsaaNameChange(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /xalia/Win32/IWin32ScrollChange.cs: -------------------------------------------------------------------------------- 1 | using Xalia.UiDom; 2 | 3 | namespace Xalia.Win32 4 | { 5 | internal interface IWin32ScrollChange : IUiDomProvider 6 | { 7 | void MsaaScrolled(int which); 8 | } 9 | } -------------------------------------------------------------------------------- /xalia/Win32/IWin32Scrollable.cs: -------------------------------------------------------------------------------- 1 | using Xalia.UiDom; 2 | 3 | namespace Xalia.Win32 4 | { 5 | internal interface IWin32Scrollable : IUiDomProvider 6 | { 7 | IUiDomProvider GetScrollBarProvider(NonclientScrollProvider nonclient); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /xalia/Win32/IWin32Styles.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xalia.UiDom; 3 | 4 | namespace Xalia.Win32 5 | { 6 | internal interface IWin32Styles : IUiDomProvider 7 | { 8 | // Adds the names of class-specific styles to names 9 | void GetStyleNames(int style, List names); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /xalia/Win32/NonclientProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Xalia.Gudl; 4 | using Xalia.UiDom; 5 | 6 | namespace Xalia.Win32 7 | { 8 | internal abstract class NonclientProvider : UiDomProviderBase 9 | { 10 | public NonclientProvider(HwndProvider hwndProvider, UiDomElement element) 11 | { 12 | HwndProvider = hwndProvider; 13 | Element = element; 14 | } 15 | 16 | public HwndProvider HwndProvider { get; } 17 | public IntPtr Hwnd => HwndProvider.Hwnd; 18 | public UiDomElement Element { get; } 19 | public Win32Connection Connection => HwndProvider.Connection; 20 | public int Pid => HwndProvider.Pid; 21 | public int Tid => HwndProvider.Tid; 22 | public CommandThread CommandThread => HwndProvider.CommandThread; 23 | 24 | public override UiDomValue EvaluateIdentifier(UiDomElement element, string identifier, HashSet<(UiDomElement, GudlExpression)> depends_on) 25 | { 26 | switch (identifier) 27 | { 28 | case "is_win32_subelement": 29 | case "is_win32_nonclient": 30 | return UiDomBoolean.True; 31 | } 32 | return base.EvaluateIdentifier(element, identifier, depends_on); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /xalia/Win32/Win32ItemRects.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using Xalia.Gudl; 7 | using Xalia.UiDom; 8 | using static Xalia.Interop.Win32; 9 | 10 | namespace Xalia.Win32 11 | { 12 | internal class Win32ItemRects : UiDomValue 13 | { 14 | private RECT[] Rects; 15 | 16 | public Win32ItemRects(RECT[] rects) 17 | { 18 | Rects = rects; 19 | } 20 | 21 | public override string ToString() 22 | { 23 | StringBuilder result = new StringBuilder(); 24 | result.Append("win32_item_rects["); 25 | bool first = true; 26 | foreach (var rect in Rects) 27 | { 28 | if (!first) 29 | result.Append("; "); 30 | result.Append($"({rect.left},{rect.top}-{rect.right},{rect.bottom})"); 31 | first = true; 32 | } 33 | result.Append(']'); 34 | return result.ToString(); 35 | } 36 | 37 | public override int GetHashCode() 38 | { 39 | return typeof(Win32ItemRects).GetHashCode() ^ 40 | StructuralComparisons.StructuralEqualityComparer.GetHashCode(Rects); ; 41 | } 42 | 43 | public override bool Equals(object obj) 44 | { 45 | if (obj is Win32ItemRects other) 46 | { 47 | if (Rects.Length != other.Rects.Length) 48 | return false; 49 | for (int i = 0; i < Rects.Length; i++) 50 | { 51 | if (!Rects[i].Equals(other.Rects[i])) 52 | return false; 53 | } 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | protected override UiDomValue EvaluateApply(UiDomValue context, GudlExpression[] arglist, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 60 | { 61 | if (arglist.Length != 1) 62 | return UiDomUndefined.Instance; 63 | 64 | var arg = context.Evaluate(arglist[0], root, depends_on); 65 | 66 | int index; 67 | if (arg.TryToInt(out int i)) 68 | { 69 | index = i; 70 | } 71 | else 72 | return UiDomUndefined.Instance; 73 | 74 | if (0 < index && index < Rects.Length) 75 | { 76 | return new Win32Rect(Rects[index]); 77 | } 78 | 79 | return base.EvaluateApply(context, arglist, root, depends_on); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /xalia/Win32/Win32Rect.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices; 3 | using Xalia.Gudl; 4 | using Xalia.UiDom; 5 | using static Xalia.Interop.Win32; 6 | 7 | namespace Xalia.Win32 8 | { 9 | internal class Win32Rect : UiDomValue 10 | { 11 | private RECT Rect; 12 | 13 | public Win32Rect(RECT rect) 14 | { 15 | Rect = rect; 16 | } 17 | 18 | public override string ToString() 19 | { 20 | return $"({Rect.left},{Rect.top}-{Rect.right},{Rect.bottom})"; 21 | } 22 | 23 | public override int GetHashCode() 24 | { 25 | return typeof(Win32Rect).GetHashCode() ^ (Rect.left, Rect.top, Rect.right, Rect.bottom).GetHashCode(); 26 | } 27 | 28 | public override bool Equals(object obj) 29 | { 30 | if (obj is Win32Rect other) 31 | { 32 | return Rect.Equals(other.Rect); 33 | } 34 | return false; 35 | } 36 | 37 | protected override UiDomValue EvaluateIdentifierCore(string id, UiDomRoot root, [In, Out] HashSet<(UiDomElement, GudlExpression)> depends_on) 38 | { 39 | switch (id) 40 | { 41 | case "left": 42 | case "x": 43 | return new UiDomInt(Rect.left); 44 | case "top": 45 | case "y": 46 | return new UiDomInt(Rect.top); 47 | case "width": 48 | return new UiDomInt(Rect.right - Rect.left); 49 | case "height": 50 | return new UiDomInt(Rect.bottom - Rect.top); 51 | case "right": 52 | return new UiDomInt(Rect.right); 53 | case "bottom": 54 | return new UiDomInt(Rect.bottom); 55 | } 56 | return base.EvaluateIdentifierCore(id, root, depends_on); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /xalia/XKeyCodes.cs: -------------------------------------------------------------------------------- 1 | namespace Xalia 2 | { 3 | internal class XKeyCodes 4 | { 5 | public static int GetKeySym(string name) 6 | { 7 | switch (name) 8 | { 9 | // TODO: fill this out? 10 | case "space": 11 | return 0x20; 12 | case "back_space": 13 | case "backspace": 14 | return 0xff08; 15 | case "tab": 16 | return 0xff09; 17 | case "enter": 18 | case "return": 19 | return 0xff0d; 20 | case "pause": 21 | return 0xff13; 22 | case "esc": 23 | case "escape": 24 | return 0xff1b; 25 | case "home": 26 | return 0xff50; 27 | case "left": 28 | return 0xff51; 29 | case "up": 30 | return 0xff52; 31 | case "right": 32 | return 0xff53; 33 | case "down": 34 | return 0xff54; 35 | case "page_up": 36 | case "pageup": 37 | return 0xff55; 38 | case "page_down": 39 | case "pagedown": 40 | return 0xff56; 41 | case "end": 42 | return 0xff57; 43 | case "insert": 44 | return 0xff63; 45 | case "del": 46 | case "delete": 47 | return 0xffff; 48 | } 49 | if (name.Length == 1) 50 | return name[0]; 51 | return 0; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /xalia/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | true 47 | true 48 | 49 | 50 | 51 | 52 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /xalia/xalia-netcore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | bin-netcore-linux 4 | obj-netcore-linux\ 5 | packages-netcore-linux\ 6 | 7 | 8 | bin-netcore-windows 9 | obj-netcore-windows\ 10 | packages-netcore-windows\ 11 | 12 | 13 | 14 | Exe 15 | Xalia 16 | disable 17 | disable 18 | False 19 | Xalia.MainClass 20 | xalia 21 | True 22 | Debug-Windows;Release-Windows;Debug-Linux;Release-Linux 23 | 24 | 25 | $(DefineConstants);DEBUG 26 | 27 | 28 | $(DefineConstants);LINUX 29 | net9.0 30 | 31 | 32 | 33 | 34 | 35 | 36 | $(DefineConstants);WINDOWS;WINFORMS 37 | net9.0-windows 38 | true 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | PreserveNewest 97 | 98 | 99 | PreserveNewest 100 | 101 | 102 | PreserveNewest 103 | 104 | 105 | 106 | 107 | --------------------------------------------------------------------------------