Compare commits

..

No commits in common. "release" and "release/4.2.1" have entirely different histories.

20 changed files with 131 additions and 415 deletions

View file

@ -1,4 +1,4 @@
[*]
[*]
guidelines = 80
end_of_line = lf
insert_final_newline = true
@ -6,9 +6,3 @@ 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

@ -1,33 +0,0 @@
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
View file

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

View file

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

View file

@ -1,30 +0,0 @@
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

View file

@ -1,21 +0,0 @@
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,26 +1,4 @@
# 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)
# 4.2.1 (2021-12-24)
## Fixed

View file

@ -1,25 +0,0 @@
<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>

View file

@ -1,26 +0,0 @@
// <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,6 +25,3 @@ 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

Binary file not shown.

281
bindED.cs
View file

@ -1,23 +1,4 @@
// <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
#nullable enable
using System;
using System.Collections.Generic;
@ -29,131 +10,102 @@ 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 readonly Version VERSION = new ("5.0.1");
private static readonly string BindingsDir = Path.Combine(
Environment.GetFolderPath(
private static dynamic? _VA;
private static string? _pluginPath;
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 ();
[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;
@"Frontier Developments\Elite Dangerous\Options\Bindings"
);
private static readonly Dictionary<string, int> _fileEventCount = new Dictionary<string, int>();
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";
/// <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 string VA_DisplayInfo() => "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n20202021 alterNERDtive";
/// <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}");
public static Guid VA_Id() => new Guid("{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.ToString());
VA.SetText("bindED.fork", "alterNERDtive");
_VA.SetText("bindED.version", VERSION);
_VA.SetText("bindED.fork", "alterNERDtive");
try
{
@ -170,38 +122,32 @@ 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")
@ -210,16 +156,7 @@ namespace bindEDplugin
}
else
{
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.");
LogError($"Invalid plugin context {context}.");
}
}
catch (Exception e)
@ -228,26 +165,11 @@ namespace bindEDplugin
}
}
/// <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_StopCommand() { }
/// <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 VA_Exit1(dynamic vaProxy) { }
private static void TextVariableChanged(string name, string from, string to, Guid? internalID = null)
public static void TextVariableChanged(string name, string from, string to, Guid? internalID)
{
if (name == "bindED.layout#")
{
@ -266,22 +188,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");
}
private static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
public 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'.");
}
@ -293,7 +215,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
{
@ -309,32 +231,31 @@ 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 (256);
List<string> missing = new List<string>(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
@ -343,45 +264,40 @@ 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 (256);
foreach (string line in File.ReadAllLines(mapFile, System.Text.Encoding.UTF8))
Dictionary<string, int> map = new Dictionary<string, int>(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.");
@ -391,8 +307,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();
@ -400,9 +316,9 @@ namespace bindEDplugin
private static string DetectBindsFile(string preset)
{
DirectoryInfo dirInfo = new (BindingsDir);
DirectoryInfo dirInfo = new DirectoryInfo(_bindingsDir);
FileInfo[] bindFiles = dirInfo.GetFiles()
.Where(i => Regex.Match(i.Name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
.Where(i => Regex.Match(i.Name, $@"^{Regex.Escape(preset)}\.[34]\.0\.binds$").Success)
.OrderByDescending(p => p.Name).ToArray();
if (bindFiles.Count() == 0)
@ -423,70 +339,61 @@ namespace bindEDplugin
rootElement = XElement.Load(file);
Dictionary<string, List<string>> binds = new (512);
Dictionary<string, List<string>> binds = new Dictionary<string, List<string>>(512);
if (rootElement != null)
{
foreach (XElement c in rootElement.Elements().Where(i => i.Elements().Count() > 0))
{
List<string> keys = new ();
List<string> keys = new List<string>();
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
@ -503,11 +410,21 @@ namespace bindEDplugin
Preset = null;
LoadBinds(Binds);
}
else if (Regex.Match(name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
else if (Regex.Match(name, $@"{Preset}(\.[34]\.0)?\.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>
<CodeAnalysisRuleSet />
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -30,7 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet />
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
@ -40,7 +40,6 @@
</ItemGroup>
<ItemGroup>
<Compile Include="bindED.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@ -87,11 +86,17 @@
</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>
</Project>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -1,18 +1,11 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32519.111
# Visual Studio Version 16
VisualStudioVersion = 16.0.30517.126
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,23 +38,13 @@ 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`.
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:
The following layouts are supported out of the box:
* en-US
* en-GB
@ -72,6 +62,3 @@ 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,10 +41,21 @@ 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 (legacy) vs. Odyssey (live)
## Horizons vs. Odyssey
Horizons support has been dropped. If you still play legacy, you will have to
use bindED <5.0.
**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]]).
## 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: "origin"
remote_name: "ssh-origin"
theme:
name: readthedocs

View file

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

View file

@ -1,16 +0,0 @@
{
"$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"
}
}
}
}