Compare commits

...

22 commits

Author SHA1 Message Date
168a4dd30b
[fix]: updated version number and copyright
Some checks failed
Create release on tag push / Create mod release (push) Failing after 28s
2024-05-03 14:33:14 +02:00
bb0f4608bf
[docs]: updated for 5.0 and dropped legacy support 2024-05-03 14:19:57 +02:00
35120ad6b8
[fix] fix for new file naming convention
Some checks failed
Create release on tag push / Create mod release (push) Failing after 52s
Also drops legacy support.

fixes #42
2024-05-03 14:05:46 +02:00
a865d1b356
chore: get rid of auto pull request workflow 2024-02-18 23:22:28 +01:00
211812427b
chore: forgejo workflows 2024-02-18 23:19:14 +01:00
alterNERDtive
75e8388ba1
Merge pull request #35 from alterNERDtive/feature/auto-pull-request
Some checks failed
Pull Request on Branch Push / Open pull request (push) Failing after 17s
2022-07-13 22:20:08 +02:00
8b189fb658
workflows: open pr on branch push
Some checks failed
Pull Request on Branch Push / Open pull request (push) Failing after 27s
2022-07-13 22:18:54 +02:00
4cbff59b33 fixed gh-pages workflow 2022-07-11 12:10:35 +02:00
5dfd9f18b4 Merge branch 'release' into develop 2022-07-04 01:34:04 +02:00
2ff49dd98a added ko-fi / sponsors 2022-07-04 01:33:53 +02:00
bbd7f443d3 dependabot 2022-06-01 09:03:21 +02:00
96bea9a127 auto-deploy gh-pages on release 2022-06-01 09:03:15 +02:00
495320c420 4.2.2! 2022-05-31 19:03:54 +02:00
bdf3c58241 docs: clarified that the default layout will work for most keys out of the box
fixes #32
2022-05-30 23:55:41 +02:00
4fb8cc0bcc one more stylecop concession 2022-05-30 23:52:44 +02:00
e845a32cd8 first naïve version of github actions for automated releases 2022-05-30 20:46:08 +02:00
57fdc0a2b7 fixed setting version variable 2022-05-29 23:41:13 +02:00
31894129c7 stylecop has joined the server 2022-05-29 23:32:31 +02:00
514f70ddb2 docs: now mentions the reports profile in the “Using Binds in Commands” section 2022-05-29 10:38:35 +02:00
27ba1a1c96 updated CHANGELOG
because I’m a dum dum and forgot
2022-01-12 16:11:03 +01:00
175d04c129 added info about generally not having to invoke the plugin manually to invalid context error message 2022-01-12 16:08:05 +01:00
80c23b3fb5 added specific error message for invoking without context
fixes #30
2022-01-12 16:07:34 +01:00
20 changed files with 415 additions and 131 deletions

View file

@ -1,4 +1,4 @@
[*]
[*]
guidelines = 80
end_of_line = lf
insert_final_newline = true
@ -6,3 +6,9 @@ charset = utf-8-bom
[*.cs]
guidelines = 80, 120
# IDE0021: Use block body for constructors
csharp_style_expression_bodied_constructors = when_on_single_line
# IDE0024: Use block body for operators
csharp_style_expression_bodied_operators = when_on_single_line

View file

@ -0,0 +1,33 @@
name: Create release on tag push
on:
push:
tags:
- 'release/*'
jobs:
build:
name: Create mod release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Get release body
run: |
echo "release_body=$(cat CHANGELOG.md)" >> "$GITHUB_ENV"
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Draft release
uses: https://gitea.com/actions/release-action@main
with:
body: ${{ env.release_body }}
draft: true
api_key: '${{ secrets.RELEASE_TOKEN }}'

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
github: alterNERDtive
ko_fi: alterNERDtive

12
.github/dependabot.yaml vendored Normal file
View file

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/"
target-branch: "develop"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "develop"
schedule:
interval: "daily"

30
.github/workflows/create-release.yaml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Create release on tag push
on:
push:
tags:
- 'release/*'
jobs:
build:
name: Build bindED
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
- name: Build
run: msbuild -t:build -p:configuration=release
- name: Draft release
uses: ncipollo/release-action@v1
with:
artifacts: "bindEDplugin.zip"
bodyFile: "CHANGELOG.md"
draft: true

21
.github/workflows/gh-pages.yaml vendored Normal file
View file

@ -0,0 +1,21 @@
name: Deploy github pages on tag push
on:
push:
tags:
- 'release/*'
jobs:
build:
name: Deploy documentation
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@nomaterial
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REQUIREMENTS: requirements.txt

View file

@ -1,4 +1,26 @@
# 4.2.1 (2021-12-24)
# 5.0.0 (2024-05-03)
## Removed
* Will no longer copy Odyssey (live) binds to Horizons (legacy) binds. I doubt
anyone still plays the latter; if you do, dont upgrade the pulgin.
## Fixed
* Will now find binds files again for the latest Elite update which changed
the format to `<name>.4.1.binds`.
# 4.2.2 (2022-05-31)
## Fixed
* Added specific error message for invoking the plugin without context (#30).
* Clarified that the default `en-US` layout will wrok for most keys on most
layouts. (#32)
-----
# 4.2.1 (2021-12-24)
## Fixed

25
Directory.build.props Normal file
View file

@ -0,0 +1,25 @@
<Project>
<PropertyGroup>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- StyleCop Analyzers configuration -->
<PropertyGroup>
<CodeAnalysisRuleSet>$(SolutionDir)StyleCop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers.Error" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.CSharp.Async.Rules" Version="6.1.41">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<AdditionalFiles Include="$(SolutionDir)stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

26
GlobalSuppressions.cs Normal file
View file

@ -0,0 +1,26 @@
// <copyright file="GlobalSuppressions.cs" company="alterNERDtive">
// Copyright 20202022 alterNERDtive.
//
// This file is part of bindED VoiceAttack plugin.
//
// bindED VoiceAttack plugin is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// bindED VoiceAttack plugin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with bindED VoiceAttack plugin. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:bindEDplugin")]

View file

@ -25,3 +25,6 @@ issue](https://github.com/alterNERDtive/bindED/issues/new). Thanks! :)
You can also [say “Hi” on Discord](https://discord.gg/YeXh2s5UC6) if that is
your thing.
[![GitHub Sponsors](https://img.shields.io/github/sponsors/alterNERDtive?style=for-the-badge)](https://github.com/sponsors/alterNERDtive)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S1DLYBS)

0
StyleCop.ruleset Normal file
View file

BIN
VoiceAttack.exe Normal file

Binary file not shown.

281
bindED.cs
View file

@ -1,4 +1,23 @@
#nullable enable
// <copyright file="bindED.cs" company="alterNERDtive">
// Copyright 20202024 alterNERDtive.
//
// This file is part of bindED VoiceAttack plugin.
//
// bindED VoiceAttack plugin is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// bindED VoiceAttack plugin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with bindED VoiceAttack plugin. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using System.Collections.Generic;
@ -10,102 +29,131 @@ using System.Xml.Linq;
namespace bindEDplugin
{
/// <summary>
/// This VoiceAttack plugin reads Elite Dangerous .binds files for keyboard
/// bindings and makes them available in VoiceAttack variables.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "historic, grandfathered in")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "historic, grandfathered in")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "historic, grandfathered in")]
public class bindEDPlugin
{
private static dynamic? _VA;
private static string? _pluginPath;
private static readonly string _bindingsDir = Path.Combine(Environment.GetFolderPath(
private static readonly Version VERSION = new ("5.0.1");
private static readonly string BindingsDir = Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData),
@"Frontier Developments\Elite Dangerous\Options\Bindings"
);
private static readonly Dictionary<string, int> _fileEventCount = new Dictionary<string, int>();
@"Frontier Developments\Elite Dangerous\Options\Bindings");
private static readonly Dictionary<string, int> FileEventCount = new ();
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "just cause")]
private static dynamic? VA;
private static string? pluginPath;
private static FileSystemWatcher? bindsWatcher;
private static FileSystemWatcher? mapWatcher;
private static string? layout;
private static Dictionary<string, int>? keyMap;
private static string? preset;
private static Dictionary<string, List<string>>? binds;
private static FileSystemWatcher BindsWatcher
{
get
{
if (_bindsWatcher == null)
if (bindsWatcher == null)
{
_bindsWatcher = new FileSystemWatcher(_bindingsDir);
_bindsWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
_bindsWatcher.Changed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_bindsWatcher.Created += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_bindsWatcher.Renamed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
bindsWatcher = new FileSystemWatcher(BindingsDir);
bindsWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
bindsWatcher.Changed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
bindsWatcher.Created += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
bindsWatcher.Renamed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
}
return _bindsWatcher!;
return bindsWatcher!;
}
}
private static FileSystemWatcher? _bindsWatcher;
private static FileSystemWatcher MapWatcher
{
get
{
if (_mapWatcher == null)
if (mapWatcher == null)
{
_mapWatcher = new FileSystemWatcher(_pluginPath);
_mapWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
_mapWatcher.Changed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_mapWatcher.Created += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_mapWatcher.Renamed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
mapWatcher = new FileSystemWatcher(pluginPath);
mapWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
mapWatcher.Changed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
mapWatcher.Created += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
mapWatcher.Renamed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
}
return _mapWatcher!;
return mapWatcher!;
}
}
private static FileSystemWatcher? _mapWatcher;
private static string? Layout
{
get => _layout ??= _VA?.GetText("bindED.layout#") ?? "en-us";
get => layout ??= VA?.GetText("bindED.layout#") ?? "en-us";
set
{
_layout = value;
layout = value;
KeyMap = null;
}
}
private static string? _layout;
private static Dictionary<string, int>? KeyMap
{
get => _keyMap ??= LoadKeyMap(Layout!);
set => _keyMap = value;
get => keyMap ??= LoadKeyMap(Layout!);
set => keyMap = value;
}
private static Dictionary<string, int>? _keyMap;
private static string? Preset
{
get => _preset ??= DetectPreset();
get => preset ??= DetectPreset();
set
{
_preset = value;
preset = value;
Binds = null;
}
}
private static string? _preset;
private static Dictionary<string, List<string>>? Binds
{
get => _binds ??= ReadBinds(DetectBindsFile(Preset!));
set => _binds = value;
get => binds ??= ReadBinds(DetectBindsFile(Preset!));
set => binds = value;
}
private static Dictionary<string, List<string>>? _binds;
public static string VERSION = "4.2.1";
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName() => $"bindED Plugin v{VERSION}-alterNERDtive";
public static string VA_DisplayInfo() => "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n20202021 alterNERDtive";
/// <summary>
/// The plugins description, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The description.</returns>
public static string VA_DisplayInfo() => "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n20202024 alterNERDtive";
public static Guid VA_Id() => new Guid("{524B4B9A-3965-4045-A39A-A239BF6E2838}");
/// <summary>
/// The plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public static Guid VA_Id() => new ("{524B4B9A-3965-4045-A39A-A239BF6E2838}");
/// <summary>
/// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Init1(dynamic vaProxy)
{
_VA = vaProxy;
_VA.TextVariableChanged += new Action<string, string, string, Guid?>(TextVariableChanged);
_pluginPath = Path.GetDirectoryName(_VA.PluginPath());
VA = vaProxy;
VA.TextVariableChanged += new Action<string, string, string, Guid?>(TextVariableChanged);
pluginPath = Path.GetDirectoryName(VA.PluginPath());
_VA.SetText("bindED.version", VERSION);
_VA.SetText("bindED.fork", "alterNERDtive");
VA.SetText("bindED.version", VERSION.ToString());
VA.SetText("bindED.fork", "alterNERDtive");
try
{
@ -122,32 +170,38 @@ namespace bindEDplugin
}
}
/// <summary>
/// The Invoke method, as required by the VoiceAttack plugin API.
/// Runs whenever a plugin context is invoked.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Invoke1(dynamic vaProxy)
{
_VA = vaProxy;
VA = vaProxy;
try
{
string context = _VA.Context.ToLower();
string context = VA.Context.ToLower();
if (context == "diagnostics")
{
LogInfo($"current keybord layout: {Layout}");
LogInfo($"current preset: {Preset}");
LogInfo($"detected binds file: {(new FileInfo(DetectBindsFile(Preset!))).Name}");
LogInfo($"detected binds file: {new FileInfo(DetectBindsFile(Preset!)).Name}");
LogInfo($"detected binds file (full path): {DetectBindsFile(Preset!)}");
}
else if (context == "listbinds")
{
ListBinds(Binds, _VA.GetText("bindED.separator") ?? "\r\n");
ListBinds(Binds, VA.GetText("bindED.separator") ?? "\r\n");
}
else if (context == "loadbinds")
{
// force reset everything
Layout = null;
Preset = null;
if (!String.IsNullOrWhiteSpace(_VA.GetText("~bindsFile")))
if (!string.IsNullOrWhiteSpace(VA.GetText("~bindsFile")))
{
Binds = ReadBinds(Path.Combine(_bindingsDir, _VA.GetText("~bindsFile")));
Binds = ReadBinds(Path.Combine(BindingsDir, VA.GetText("~bindsFile")));
}
LoadBinds(Binds);
}
else if (context == "missingbinds")
@ -156,7 +210,16 @@ namespace bindEDplugin
}
else
{
LogError($"Invalid plugin context {context}.");
if (string.IsNullOrWhiteSpace(context))
{
LogError("Empty plugin context.");
}
else
{
LogError($"Invalid plugin context '{context}'.");
}
LogError("You generally do not need to invoke the plugin manually.");
}
}
catch (Exception e)
@ -165,11 +228,26 @@ namespace bindEDplugin
}
}
public static void VA_StopCommand() { }
/// <summary>
/// The Exit method, as required by the VoiceAttack plugin API.
/// Runs when VoiceAttack is shut down.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
public static void VA_Exit1(dynamic vaProxy)
{
}
public static void VA_Exit1(dynamic vaProxy) { }
/// <summary>
/// The StopCommand method, as required by the VoiceAttack plugin API.
/// Runs whenever all commands are stopped using the “Stop All Commands”
/// button or action.
/// </summary>
public static void VA_StopCommand()
{
}
public static void TextVariableChanged(string name, string from, string to, Guid? internalID)
private static void TextVariableChanged(string name, string from, string to, Guid? internalID = null)
{
if (name == "bindED.layout#")
{
@ -188,22 +266,22 @@ namespace bindEDplugin
private static void LogError(string message)
{
_VA!.WriteToLog($"ERROR | bindED: {message}", "red");
VA!.WriteToLog($"ERROR | bindED: {message}", "red");
}
private static void LogInfo(string message)
{
_VA!.WriteToLog($"INFO | bindED: {message}", "blue");
VA!.WriteToLog($"INFO | bindED: {message}", "blue");
}
private static void LogWarn(string message)
{
_VA!.WriteToLog($"WARN | bindED: {message}", "yellow");
VA!.WriteToLog($"WARN | bindED: {message}", "yellow");
}
public static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
private static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
{
_VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys));
VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys));
LogInfo("List of Elite binds saved to TXT variable '~bindED.bindsList'.");
}
@ -215,7 +293,7 @@ namespace bindEDplugin
bool valid = true;
if (bind.Value.Count == 0)
{
//LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …");
// LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …");
}
else
{
@ -231,31 +309,32 @@ namespace bindEDplugin
LogError($"No valid key code for '{key}' found, skipping bind for '{bind.Key}' …");
}
}
if (valid)
{
_VA!.SetText(bind.Key, value);
VA!.SetText(bind.Key, value);
}
}
}
LogInfo($"Elite binds '{(String.IsNullOrWhiteSpace(_VA!.GetText("~bindsFile")) ? Preset : _VA!.GetText("~bindsFile"))}' for layout '{Layout}' loaded successfully.");
LogInfo($"Elite binds '{(string.IsNullOrWhiteSpace(VA!.GetText("~bindsFile")) ? Preset : VA!.GetText("~bindsFile"))}' for layout '{Layout}' loaded successfully.");
}
private static void MissingBinds(Dictionary<string, List<string>>? binds)
{
List<string> missing = new List<string>(256);
List<string> missing = new (256);
foreach (KeyValuePair<string, List<string>> bind in binds!)
{
if (bind.Value.Count == 0)
{
missing.Add(bind.Key);
}
}
if (missing.Count > 0)
{
_VA!.SetText("~bindED.missingBinds", string.Join("\r\n", missing));
_VA!.SetBoolean("~bindED.missingBinds", true);
VA!.SetText("~bindED.missingBinds", string.Join("\r\n", missing));
VA!.SetBoolean("~bindED.missingBinds", true);
LogInfo("List of missing Elite binds saved to TXT variable '~bindED.missingBinds'.");
}
else
@ -264,40 +343,45 @@ namespace bindEDplugin
}
}
private static Dictionary<String, int> LoadKeyMap(string layout)
private static Dictionary<string, int> LoadKeyMap(string layout)
{
string mapFile = Path.Combine(_pluginPath, $"EDMap-{layout.ToLower()}.txt");
string mapFile = Path.Combine(pluginPath, $"EDMap-{layout.ToLower()}.txt");
if (!File.Exists(mapFile))
{
throw new FileNotFoundException($"No map file for layout '{layout}' found.");
}
Dictionary<string, int> map = new Dictionary<string, int>(256);
foreach (String line in File.ReadAllLines(mapFile, System.Text.Encoding.UTF8))
Dictionary<string, int> map = new (256);
foreach (string line in File.ReadAllLines(mapFile, System.Text.Encoding.UTF8))
{
String[] arItem = line.Split(";".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries);
if ((arItem.Count() == 2) && (!String.IsNullOrWhiteSpace(arItem[0])) && (!map.ContainsKey(arItem[0])))
string[] arItem = line.Split(";".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries);
if ((arItem.Count() == 2) && (!string.IsNullOrWhiteSpace(arItem[0])) && (!map.ContainsKey(arItem[0])))
{
ushort iKey;
if (ushort.TryParse(arItem[1], out iKey))
{
if (iKey > 0 && iKey < 256)
{
map.Add(arItem[0].Trim(), iKey);
}
}
}
}
if (map.Count == 0)
{
throw new Exception($"Map file for {layout} does not contain any elements.");
}
return map;
}
private static string DetectPreset()
{
string startFile = Path.Combine(_bindingsDir, "StartPreset.4.start");
string startFile = Path.Combine(BindingsDir, "StartPreset.4.start");
if (!File.Exists(startFile))
{
startFile = Path.Combine(_bindingsDir, "StartPreset.start");
startFile = Path.Combine(BindingsDir, "StartPreset.start");
if (!File.Exists(startFile))
{
throw new FileNotFoundException("No 'StartPreset.start' file found. Please run Elite: Dangerous at least once, then restart VoiceAttack.");
@ -307,8 +391,8 @@ namespace bindEDplugin
IEnumerable<string> presets = File.ReadAllLines(startFile).Distinct();
if (presets.Count() > 1)
{
LogError($"You have selected multiple control presets ('{ String.Join("', '", presets) }'). "
+ $"Only binds from '{ presets.First() }' will be used. Please refer to the documentation for more information.");
LogError($"You have selected multiple control presets ('{string.Join("', '", presets)}'). "
+ $"Only binds from '{presets.First()}' will be used. Please refer to the documentation for more information.");
}
return presets.First();
@ -316,9 +400,9 @@ namespace bindEDplugin
private static string DetectBindsFile(string preset)
{
DirectoryInfo dirInfo = new DirectoryInfo(_bindingsDir);
DirectoryInfo dirInfo = new (BindingsDir);
FileInfo[] bindFiles = dirInfo.GetFiles()
.Where(i => Regex.Match(i.Name, $@"^{Regex.Escape(preset)}\.[34]\.0\.binds$").Success)
.Where(i => Regex.Match(i.Name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
.OrderByDescending(p => p.Name).ToArray();
if (bindFiles.Count() == 0)
@ -339,61 +423,70 @@ namespace bindEDplugin
rootElement = XElement.Load(file);
Dictionary<string, List<string>> binds = new Dictionary<string, List<string>>(512);
Dictionary<string, List<string>> binds = new (512);
if (rootElement != null)
{
foreach (XElement c in rootElement.Elements().Where(i => i.Elements().Count() > 0))
{
List<string> keys = new List<string>();
List<string> keys = new ();
foreach (var element in c.Elements().Where(i => i.HasAttributes))
{
if (element.Name == "Primary")
{
if (element.Attribute("Device").Value == "Keyboard" && !String.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
if (element.Attribute("Device").Value == "Keyboard"
&& !string.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
{
foreach (var modifier in element.Elements().Where(i => i.Name.LocalName == "Modifier"))
{
keys.Add(modifier.Attribute("Key").Value);
}
keys.Add(element.Attribute("Key").Value);
}
}
if (keys.Count == 0 && element.Name == "Secondary") //nothing found in primary... look in secondary
{
if (element.Attribute("Device").Value == "Keyboard" && !String.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
if (keys.Count == 0 && element.Name == "Secondary")
{ // nothing found in primary... look in secondary
if (element.Attribute("Device").Value == "Keyboard"
&& !string.IsNullOrWhiteSpace(element.Attribute("Key").Value) && element.Attribute("Key").Value.StartsWith("Key_"))
{
foreach (var modifier in element.Elements().Where(i => i.Name.LocalName == "Modifier"))
{
keys.Add(modifier.Attribute("Key").Value);
}
keys.Add(element.Attribute("Key").Value);
}
}
}
binds.Add($"ed{c.Name.LocalName}", keys);
}
}
return binds;
}
private static void FileChangedHandler(string name)
{
// so apparently these events all fire twice … lets make sure we only handle it once.
if (_fileEventCount.ContainsKey(name))
if (FileEventCount.ContainsKey(name))
{
_fileEventCount[name] += 1;
FileEventCount[name] += 1;
}
else
{
_fileEventCount.Add(name, 1);
FileEventCount.Add(name, 1);
}
if (_fileEventCount[name] % 2 == 0)
if (FileEventCount[name] % 2 == 0)
{
try
{
// lets make semi-sure that the file isnt locked …
// FIXXME: solve this properly
Thread.Sleep(500);
// Going by name only is a bit naïve given were watching 2
// separate directories, but hey … worst case if something
// is doing unintended things is unnecessarily reloading the
@ -410,21 +503,11 @@ namespace bindEDplugin
Preset = null;
LoadBinds(Binds);
}
else if (Regex.Match(name, $@"{Preset}(\.[34]\.0)?\.binds$").Success)
else if (Regex.Match(name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
{
LogInfo($"Bindings file '{name}' has changed, reloading …");
Binds = null;
LoadBinds(Binds);
// copy Odyssey -> Horizons
if (name == $"{Preset}.4.0.binds" && !_VA!.GetBoolean("bindED.disableHorizonsSync#"))
{
File.WriteAllText(
Path.Combine(_bindingsDir, $"{Preset}.3.0.binds"),
File.ReadAllText(Path.Combine(_bindingsDir, name))
.Replace("MajorVersion=\"4\" MinorVersion=\"0\">", "MajorVersion=\"3\" MinorVersion=\"0\">")
);
}
}
}
catch (Exception e)

View file

@ -21,7 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<CodeAnalysisRuleSet />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -30,7 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<CodeAnalysisRuleSet />
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
@ -40,6 +40,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="bindED.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@ -86,17 +87,11 @@
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>powershell if (Test-Path '$(SolutionDir)bindEDplugin.zip') { Remove-Item -Path '$(SolutionDir)bindEDplugin.zip' }</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>if $(ConfigurationName) == Release (powershell Compress-Archive -Path '$(TargetDir)' -DestinationPath '$(SolutionDir)bindEDplugin.zip' -Force)</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release' ">
<ZipDirectory SourceDirectory="bin" DestinationFile="bindEDplugin.zip" Overwrite="true" />
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View file

@ -1,11 +1,18 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30517.126
# Visual Studio Version 17
VisualStudioVersion = 17.3.32519.111
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bindEDplugin", "bindEDplugin.csproj", "{C8AC9134-639D-45D2-B5EF-138E0550E0C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC89314B-E504-4D5D-BB48-F1E3E4980A13}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\create-release.yaml = .github\workflows\create-release.yaml
Directory.build.props = Directory.build.props
GlobalSuppressions.cs = GlobalSuppressions.cs
stylecop.json = stylecop.json
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -38,13 +38,23 @@ have to create a `Key Press` action and use the `edLandingGearToggle` variable.
![[keypress.png]]
Instead of looking through a binds file to find variable names, you can also
import and run the `bindED-reports` profile from the plugin directory. It will
put a) a list of all binds and b) a list of all missing binds on your Desktop.
Currently these lists will include _all_ binds, including axes that cannot be
bound to the keyboard.
## Supported Keyboard Layouts
If you are using any non-US layout you might have noticed that some binds dont
work. You can set a text variable in VoiceAttack called `bindED.layout#` to the
layout you want to use. If the variable is not set it will defaut to `en-us`.
The following layouts are supported out of the box:
This default should work for most™ keys on any keyboard layout natively
supported by Elite Dangerous; some special keys might not be usable.
The following layouts are _fully_ supported out of the box:
* en-US
* en-GB
@ -62,3 +72,6 @@ issue](https://github.com/alterNERDtive/bindED/issues/new). Thanks! :)
You can also [say “Hi” on Discord](https://discord.gg/YeXh2s5UC6) if that is
your thing.
[![GitHub Sponsors](https://img.shields.io/github/sponsors/alterNERDtive?style=for-the-badge)](https://github.com/sponsors/alterNERDtive)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S1DLYBS)

View file

@ -41,21 +41,10 @@ have to either invoke the plugins `loadbinds` config manually or restart
VoiceAttack after you have loaded into the games main menu and changed any key
bind.
## Horizons vs. Odyssey
## Horizons (legacy) vs. Odyssey (live)
**Note**: If you do not own Odyssey, everything will work just as before!
Sadly for the time being Odyssey and Horizons will basically be separate games.
That also means they have separate binds files. BindED will always default to
using the file generated by Odyssey (`<preset>.4.0.binds`) if it exists.
To keep hassle to a minimum, the recommended way to change binds is to do it
from Odyssey. Whenever a change to the Odyssey file is detected, the plugin will
overwrite Horizons binds (`<preset>.3.0.binds`) with it. If you for some reason
want to keep entirely separate binds, you can set `bindED.disableHorizonsSync#`
(yes, including the pound sign) to `true` in your VoiceAttack profile. Whenever
you are playing Horizons you will have to tell the plugin to load the Horizons
file (see [[#Specifying a Binds File to Load]]).
Horizons support has been dropped. If you still play legacy, you will have to
use bindED <5.0.
## Specifying a Binds File to Load

View file

@ -4,7 +4,7 @@ repo_url: https://github.com/alterNERDtive/bindED
edit_uri: "edit/devel/docs/"
site_description: "This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as VoiceAttack variables."
site_author: "alterNERDtive"
remote_name: "ssh-origin"
remote_name: "origin"
theme:
name: readthedocs

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
mkdocs-roamlinks-plugin

16
stylecop.json Normal file
View file

@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace"
},
"documentationRules": {
"companyName": "alterNERDtive",
"copyrightText": "Copyright {year} {companyName}.\n\nThis file is part of {application}.\n\n{application} is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\n{application} is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with {application}. If not, see <https://www.gnu.org/licenses/>.",
"variables": {
"application": "bindED VoiceAttack plugin",
"year": "20202022"
}
}
}
}