├── .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 |
--------------------------------------------------------------------------------