Compare commits

...

57 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
ef25bcd7bf Added support for Odyssey’s new StartPreset.4.start file
fixes #29
2021-12-24 17:50:34 +01:00
c83848c0e3 README: typo fixed 2021-10-11 22:08:40 +02:00
4412c1e59e README: typo fixed 2021-09-08 08:41:22 +02:00
8f8eb81fbd Merge branch 'develop' into release 2021-08-13 22:00:22 +02:00
61cd2d14fd 4.2! 2021-08-13 21:43:24 +02:00
b177fa4e1c nullable preset in DetectBindsFile() doesn’t make sense 2021-07-18 19:57:25 +02:00
856b95974c now correctly loads presets with regex special characters
fixes #28
2021-07-18 19:56:36 +02:00
b1455beddc no longer shipping old ReadMe.txt with plugin releases 2021-07-04 21:12:22 +02:00
11e4ac1575 incremented version number, because I’m a buffoon 2021-07-04 21:08:05 +02:00
83460ac9b8 docs: added link to CHANGELOG on Github 2021-07-04 20:59:36 +02:00
65534330d9 added error message if multiple presets are used
fixes #26
2021-07-04 20:59:18 +02:00
69f3a43590 docs: clarification on starting Elite first time after VA 2021-06-14 12:40:57 +02:00
f46f40abb1 docs: added actual usage instructions
fixes #24
2021-06-14 11:57:05 +02:00
9d2c5355d8 sorted out the utf-8 crap … 2021-06-14 11:46:57 +02:00
3a8d8ff620 docs: now with docs™!
also fixes #21
2021-06-14 11:32:14 +02:00
58bfdd65f1 .editorconfig: default to utf-8 2021-06-14 11:22:45 +02:00
09767d493d added diagnostics context and profile
Should help with troubleshooting.
2021-06-14 09:38:14 +02:00
3fc7e73fe8 Merge branch 'develop' into release 2021-05-23 20:22:39 +02:00
8cbf4874d7 updated .editorconfig 2021-05-23 20:22:05 +02:00
b0edff39de 4.1! 2021-05-22 18:42:19 +02:00
0652fc84ab now correctly prioritizing binds files
4.0 > 3.0 > plain
2021-05-22 18:40:50 +02:00
b99c857ccc updated preset detection for Odyssey
For now will only work with the first line; which means that Odyssey users will have to use the same preset for everything.

fixes #15
2021-05-22 18:16:28 +02:00
df5366ec4d README: updated instructions for changing Horizons binds 2021-05-22 18:09:14 +02:00
ed8ba6244c README: updated with prerequisites
You need to load the game at least once, you need to have changed at least a single bind.

fixes #19
2021-05-22 18:02:00 +02:00
d431e899bd added ~bindsFile optional parameter to loadbinds context
fixes #18
2021-05-22 17:55:34 +02:00
dadb6c8f2e Merge branch 'develop' into release 2021-05-19 12:33:24 +02:00
e63ff1bbab 4.0! 2021-05-19 12:32:01 +02:00
4cf8788049 removed deprecated empty / binds file contexts 2021-05-19 12:25:20 +02:00
bd3c5ec96f pretty printerino 2021-05-19 12:20:59 +02:00
c8b76472bb Odyssey compatibility
fixes #14
2021-05-19 12:13:14 +02:00
0736d29682 Odyssey support, quick & dirty
Just takes the last changed file of `<preset>.binds`, `<preset>.3.0.binds` or `<preset>.4.0.binds`.
2021-05-19 10:10:33 +02:00
2bb3bd947f README: added pointer to the release page.
fixes #11
2021-02-22 21:35:50 +01:00
b4059992ca README: added pointer to the release page.
fixes #11
2021-02-22 21:35:10 +01:00
47a9fd20e6 fixed version number 2021-01-30 14:36:08 +01:00
399db2b53a CHANGELOG: typo 2021-01-29 15:25:03 +01:00
25 changed files with 1494 additions and 890 deletions

View file

@ -1,7 +1,14 @@
# All files [*]
[*]
guidelines = 80 guidelines = 80
end_of_line = lf
insert_final_newline = true
charset = utf-8-bom
# C# or VB files [*.cs]
[*.{cs,vb}] guidelines = 80, 120
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

3
.gitignore vendored
View file

@ -1,5 +1,6 @@
.vs/** .vs/**
obj/** obj/**
bin/** bin/**
site/**
bindEDplugin.csproj.user bindEDplugin.csproj.user
*.zip *.zip

View file

@ -1,58 +1,174 @@
## 3.1 (2021-01-29) # 5.0.0 (2024-05-03)
## Changed ## Removed
* Invoking the `loadbinds` context will now force reset everything and reload * Will no longer copy Odyssey (live) binds to Horizons (legacy) binds. I doubt
from scratch. (#5) anyone still plays the latter; if you do, dont upgrade the pulgin.
## Added ## Fixed
* The current layouts key map file is now monitored for changes. Should make * Will now find binds files again for the latest Elite update which changed
adding support for new layouts slightly less annoying. (#4) the format to `<name>.4.1.binds`.
----- # 4.2.2 (2022-05-31)
# 3.0 (2020-11-12) ## Fixed
I did a complete refactoring of everything to prepare for some juicy new * Added specific error message for invoking the plugin without context (#30).
features! Sadly that also meant breaking backwards compatibility. On the plus * Clarified that the default `en-US` layout will wrok for most keys on most
side, the things that no longer work like they did in Garys initial release layouts. (#32)
should basically never be used anyway.
-----
## Removed
# 4.2.1 (2021-12-24)
* You can no longer specify binding files to use by linking them into the plugin
directory. ## Fixed
* You can no longer specify binding files by using them as the plugin context.
* Added support for Odysseys new `StartPreset.4.start` file; the preset
## Changed selected in the Odyssey client will take precedence, so make sure you use the
same preset for Horizons (#29).
* Invoking the plugin with no context or with a binds file as context is now
deprecated and will be removed in a future version. Use the `loadbinds` -----
context instead.
# 4.2 (2021-08-13)
## Added
## Added
* `en-gb` key map. Thank you A.Cyprus for the work on that!
* Bindings are now automagically read when VoiceAttack loads and when * Pretty printed documentation! You can find it at
`bindED.layout#` is changed. https://alterNERDtive.github.io/bindED.
* After the initial reading of bindings the plugin will monitor the bindings * Troubleshooting guide! You can find it at
directory for changes to a) the `StartPreset.start` file (preset has changed) https://alterNERDtive.github.io/bindED/troubleshooting.
and b) the binds file(s) corresponding to the current preset. Changes are * `diagnostics` plugin context: writes current plugin state to the log for
automatically applied. (#3) troubleshooting.
* The `listbinds` context will set the text variable `~bindED.bindsList` to a * `bindED-diagnostics` profile: runs the `diagnostics` plugin context on load.
list of bindings present in the current bindings file. (#1) Should be the first troubleshooting step.
* The `missingbinds` context will create a report of missing binds (anything * Error message if multiple control presets are in use (Odyssey only).
that doesnt have keyboard binds) and save it to `~bindED.missingBinds`. (#2)
* The included `bindED-reports` profile runs a missing binds report and a binds ## Fixed
list report when you load it and saves them to your Desktop.
* Now correctly loads presets that contain regex special characters (#28).
-----
-----
# 2.0 (2020-09-23)
# 4.1 (2021-05-22)
## Added
## Added
* Support for non-US keyboard layouts. `de-neo2` is included (because thats
what Im using), others can be added ([see the wiki for * optional `~bindsFile` parameter for the `loadbinds` plugin context: use that
instructions](https://github.com/alterNERDtive/bindED/wiki/Keyboard-Layouts)). to specify a binds file instead of auto-detecting it from the currently active
preset. (#18)
## Changed
* Updated the README to reflect that you need to load the game once, and that
you need to have changed at least a single bind. (#19)
* Updated the README to reflect that you have to use a single preset for all
sections if youre playing Odyssey.
## Fixed
* Now only reading the first line of `startPreset.start` to work correctly with
Odyssey. (#15)
* Now correctly prioritizing `.4.0.binds` > `.3.0.binds` > `.binds`. (#20)
-----
# 4.0 (2021-05-19)
**Note**: If you do not own Odyssey, everything will work just as before!
I, too, do not own Odyssey. So while I have tried testing various things with
mock Odyssey binds files, please keep an eye out for bugs and [file an
issue](https://github.com/alterNERDtive/bindED/issues/new/) if you
encounter any. And check back for a potential 4.0.1 soon. TYVM!
**IMPORTANT**: Please backup your binds files before installing this release,
just in case. You can find them in
`%localappdata%\Frontier Developments\Elite Dangerous\Options\Bindings`.
Sadly for the time being Odyssey and Horizons will basically be separate games.
That also means they have separate binds files.
BindED will by default always use the last edited file, be that the base preset,
Horizons or Odyssey.
To keep hassle to a minimum, the recommended way to change binds is to do it
from Odyssey. When a change to the Odyssey file is detected, the plugin will
by default overwrite Horizons binds with it. To prevent that and keep entirely
separate binds, you can set `bindED.disableHorizonsSync#` (yes, including the
pound sign) to `true` in your VoiceAttack profile.
## Added
* Odyssey binds file support (`*.4.0.binds`). (#14)
* `bindED.disableHorizonsSync#` configuration option: Set this (to `true`) in
your VoiceAttack profile to disable automatically syncing Odyssey binds
changes to Horizons binds.
## Removed
* empty plugin context: Invoking the plugin without context no longer gives a
deprecation warning and will instead fail.
* binds file as plugin context: Invoking the plugin with a binds file as context
no longer gives a deprecation warning and will instead fail.
-----
# 3.1 (2021-01-29)
## Changed
* Invoking the `loadbinds` context will now force reset everything and reload
from scratch. (#5)
## Added
* The current layouts key map file is now monitored for changes. Should make
adding support for new layouts slightly less annoying. (#4)
-----
# 3.0 (2020-11-12)
I did a complete refactoring of everything to prepare for some juicy new
features! Sadly that also meant breaking backwards compatibility. On the plus
side, the things that no longer work like they did in Garys initial release
should basically never be used anyway.
## Removed
* You can no longer specify binding files to use by linking them into the plugin
directory.
* You can no longer specify binding files by using them as the plugin context.
## Changed
* Invoking the plugin with no context or with a binds file as context is now
deprecated and will be removed in a future version. Use the `loadbinds`
context instead.
## Added
* `en-gb` key map. Thank you A.Cyprus for the work on that!
* Bindings are now automagically read when VoiceAttack loads and when
`bindED.layout#` is changed.
* After the initial reading of bindings the plugin will monitor the bindings
directory for changes to a) the `StartPreset.start` file (preset has changed)
and b) the binds file(s) corresponding to the current preset. Changes are
automatically applied. (#3)
* The `listbinds` context will set the text variable `~bindED.bindsList` to a
list of bindings present in the current bindings file. (#1)
* The `missingbinds` context will create a report of missing binds (anything
that doesnt have keyboard binds) and save it to `~bindED.missingBinds`. (#2)
* The included `bindED-reports` profile runs a missing binds report and a binds
list report when you load it and saves them to your Desktop.
-----
# 2.0 (2020-09-23)
## Added
* Support for non-US keyboard layouts. `de-neo2` is included (because thats
what Im using), others can be added ([see the wiki for
instructions](https://github.com/alterNERDtive/bindED/wiki/Keyboard-Layouts)).

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")]

7
Makefile Normal file
View file

@ -0,0 +1,7 @@
.PHONY: docs deploy-docs
docs:
mkdocs build -c
deploy-docs:
mkdocs gh-deploy -cs

108
README.md
View file

@ -1,78 +1,30 @@
# bindED # bindED
This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as
VoiceAttack variables. It has originally been written by Gary (the developer of VoiceAttack variables. It was originally written by Gary (the developer of
VoiceAttack) [and published on the VoiceAttack VoiceAttack) [and published on the VoiceAttack
forums](https://forum.voiceattack.com/smf/index.php?topic=564.0). forums](https://forum.voiceattack.com/smf/index.php?topic=564.0). You can find
the [original README here](https://alterNERDtive.github.io/bindED/ReadMe.txt)
You can find the [original README here](ReadMe.txt). for reference.
I have taken the original source code and added automatic detection of the I have basically done a complete rewrite of the original source code at this point
correct bindings file and support for non-US keyboard layouts (see below for and added a lot of features including automatic detection of the correct
details). bindings file and support for non-US keyboard layouts (see below for details).
## Migrating from the Old Plugin ## Documentation & Installation Guide
If you use this as a drop-in replacement for the initial version all commands You can find [comprehensive documentation on Github
invoking the plugin will throw an error message. Gary has asked me to change the Pages](https://alterNERDtive.github.io/bindED).
plugins GUID, and the plugin with the old one will no longer be found.
## Need Help / Want to Contribute?
_That is irrelevant in basically all cases and can safely be ignored_. Binds
will be read automatically when VoiceAttack starts, and when they change. Have a look at [the troubleshooting
guide](https://alterNERDtive.github.io/bindED/troubleshooting). If your problem
## Usage persists, please [file an
issue](https://github.com/alterNERDtive/bindED/issues/new). Thanks! :)
You dont have to do anything! When VoiceAttack loads, bindED will automatically
detect your bindings. It will also keep a watchful eye on Elites bindings You can also [say “Hi” on Discord](https://discord.gg/YeXh2s5UC6) if that is
folder and reload them when there is a change! your thing.
If something goes awry, you can still manually call the `loadbinds` plugin [![GitHub Sponsors](https://img.shields.io/github/sponsors/alterNERDtive?style=for-the-badge)](https://github.com/sponsors/alterNERDtive)
context to force a refresh. [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S1DLYBS)
If you are not using a US QWERTY keyboard layout, see below.
## Support for non-US Keyboard Layouts
Shipped layouts:
* en-US
* en-GB
* de-neo2
If you are using any non-US layout you might have noticed that some binds dont
work. Elite internally uses keycode values (a number assigned to each key on the
keyboard) for its bindings but for some reason both displays and saves them as
keysyms (the label on the key), according to the UK QWERTY keyboard layout. That
means VoiceAttack cant just send the keysym it reads from a binding, it has to
translate it into the corresponding keycode.
The original plugin contained a `EDMap.txt` file that contains information on
that conversion _for the US keyboard layout_. If you are using any other layout
that information will be incorrect for any symbols that are on a different key
than they are on the US layout.
I have added the option to use maps for other keyboard layouts. In order to do
so you will have to set a text variable in VoiceAttack called `bindED.layout#`
to the layout you want to use. BindED will be notified of the variable changing
and reload your bindings with the appropriate key map. If the variable is not
set it will defaut to “en-us”, leaving the original behaviour intact.
I have included a map file for [Neo2](https://neo-layout.org)
(`EDMap-de-neo2.txt`) which is the layout that I am using personally. If you are
on a different layout, you will have to create a corresponding map file yourself
or prod me to add it. E.g. for the french AZERTY it would be `EDMap-fr-fr.text`
and set `bindED.layout#` to “fr-fr”. For US Dvorak, `EDmap-en-us-dvorak` and
“en-us-dvorak”. You can see where this is going.
For more information on [creating new supported keyboard layouts see the
Wiki](https://github.com/alterNERDtive/bindED/wiki/Keyboard-Layouts).
## Troubleshooting
If you run into any kinds of trouble with missing bindings the first step should
be to import and load the included `bindED-reports` profile. It will generate
both a list of bind names used by Elite and a report of binds that do not have a
keyboard shortcut assigned, and put them on your Desktop.
Need help beyond that? Please [file an
issue](https://github.com/alterNERDtive/bindED/issues/new) or [hop into
Discord](https://discord.gg/YeXh2s5UC6).

0
StyleCop.ruleset Normal file
View file

BIN
VoiceAttack.exe Normal file

Binary file not shown.

Binary file not shown.

916
bindED.cs
View file

@ -1,396 +1,520 @@
#nullable enable // <copyright file="bindED.cs" company="alterNERDtive">
// Copyright 20202024 alterNERDtive.
using System; //
using System.Collections.Generic; // This file is part of bindED VoiceAttack plugin.
using System.IO; //
using System.Linq; // bindED VoiceAttack plugin is free software: you can redistribute it and/or modify
using System.Text.RegularExpressions; // it under the terms of the GNU General Public License as published by
using System.Threading; // the Free Software Foundation, either version 3 of the License, or
using System.Xml.Linq; // (at your option) any later version.
//
namespace bindEDplugin // bindED VoiceAttack plugin is distributed in the hope that it will be useful,
{ // but WITHOUT ANY WARRANTY; without even the implied warranty of
public class bindEDPlugin // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
{ // GNU General Public License for more details.
private static dynamic? _VA; //
private static string? _pluginPath; // You should have received a copy of the GNU General Public License
private static readonly string _bindingsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Frontier Developments\Elite Dangerous\Options\Bindings"); // along with bindED VoiceAttack plugin. If not, see &lt;https://www.gnu.org/licenses/&gt;.
private static readonly Dictionary<string, int> _fileEventCount = new Dictionary<string, int>(); // </copyright>
private static FileSystemWatcher BindsWatcher #nullable enable
{
get using System;
{ using System.Collections.Generic;
if (_bindsWatcher == null) using System.IO;
{ using System.Linq;
_bindsWatcher = new FileSystemWatcher(_bindingsDir); using System.Text.RegularExpressions;
_bindsWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; using System.Threading;
_bindsWatcher.Changed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); }; using System.Xml.Linq;
_bindsWatcher.Created += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
_bindsWatcher.Renamed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); }; namespace bindEDplugin
} {
return _bindsWatcher!; /// <summary>
} /// This VoiceAttack plugin reads Elite Dangerous .binds files for keyboard
} /// bindings and makes them available in VoiceAttack variables.
private static FileSystemWatcher? _bindsWatcher; /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "historic, grandfathered in")]
private static FileSystemWatcher MapWatcher [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")]
get public class bindEDPlugin
{ {
if (_mapWatcher == null) private static readonly Version VERSION = new ("5.0.1");
{
_mapWatcher = new FileSystemWatcher(_pluginPath); private static readonly string BindingsDir = Path.Combine(
_mapWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; Environment.GetFolderPath(
_mapWatcher.Changed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); }; Environment.SpecialFolder.LocalApplicationData),
_mapWatcher.Created += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); }; @"Frontier Developments\Elite Dangerous\Options\Bindings");
_mapWatcher.Renamed += (source, EventArgs) => { FileChangedHandler(EventArgs.Name); };
} private static readonly Dictionary<string, int> FileEventCount = new ();
return _mapWatcher!;
} [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 FileSystemWatcher? _mapWatcher; private static string? pluginPath;
private static FileSystemWatcher? bindsWatcher;
private static string? Layout private static FileSystemWatcher? mapWatcher;
{ private static string? layout;
get => _layout ??= _VA?.GetText("bindED.layout#") ?? "en-us"; private static Dictionary<string, int>? keyMap;
set private static string? preset;
{ private static Dictionary<string, List<string>>? binds;
_layout = value;
KeyMap = null; private static FileSystemWatcher BindsWatcher
} {
} get
private static string? _layout; {
if (bindsWatcher == null)
private static Dictionary<string, int>? KeyMap {
{ bindsWatcher = new FileSystemWatcher(BindingsDir);
get => _keyMap ??= LoadKeyMap(Layout!); bindsWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
set => _keyMap = value; bindsWatcher.Changed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
} bindsWatcher.Created += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
private static Dictionary<string, int>? _keyMap; bindsWatcher.Renamed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
}
private static string? Preset
{ return bindsWatcher!;
get => _preset ??= DetectPreset(); }
set }
{
_preset = value; private static FileSystemWatcher MapWatcher
Binds = null; {
} get
} {
private static string? _preset; if (mapWatcher == null)
{
private static Dictionary<string, List<string>>? Binds mapWatcher = new FileSystemWatcher(pluginPath);
{ mapWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
get => _binds ??= ReadBinds(DetectBindsFile(Preset)); mapWatcher.Changed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
set => _binds = value; mapWatcher.Created += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
} mapWatcher.Renamed += (source, eventArgs) => { FileChangedHandler(eventArgs.Name); };
private static Dictionary<string, List<string>>? _binds; }
public static string VERSION = "3.0"; return mapWatcher!;
}
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"; private static string? Layout
{
public static Guid VA_Id() => new Guid("{524B4B9A-3965-4045-A39A-A239BF6E2838}"); get => layout ??= VA?.GetText("bindED.layout#") ?? "en-us";
set
public static void VA_Init1(dynamic vaProxy) {
{ layout = value;
_VA = vaProxy; KeyMap = null;
_VA.TextVariableChanged += new Action<string, string, string, Guid?>(TextVariableChanged); }
_pluginPath = Path.GetDirectoryName(_VA.PluginPath()); }
_VA.SetText("bindED.version", VERSION); private static Dictionary<string, int>? KeyMap
_VA.SetText("bindED.fork", "alterNERDtive"); {
get => keyMap ??= LoadKeyMap(Layout!);
try set => keyMap = value;
{ }
LoadBinds(Binds);
} private static string? Preset
catch (Exception e) {
{ get => preset ??= DetectPreset();
LogError(e.Message); set
} {
finally preset = value;
{ Binds = null;
BindsWatcher.EnableRaisingEvents = true; }
MapWatcher.EnableRaisingEvents = true; }
}
} private static Dictionary<string, List<string>>? Binds
{
public static void VA_Invoke1(dynamic vaProxy) get => binds ??= ReadBinds(DetectBindsFile(Preset!));
{ set => binds = value;
_VA = vaProxy; }
try
{ /// <summary>
string context = _VA.Context.ToLower(); /// The plugins display name, as required by the VoiceAttack plugin API.
if (context == "listbinds") /// </summary>
{ /// <returns>The display name.</returns>
ListBinds(Binds, _VA.GetText("bindED.separator") ?? "\r\n"); public static string VA_DisplayName() => $"bindED Plugin v{VERSION}-alterNERDtive";
}
else if (context == "loadbinds") /// <summary>
{ /// The plugins description, as required by the VoiceAttack plugin API.
// force reset everything /// </summary>
Layout = null; /// <returns>The description.</returns>
Preset = null; public static string VA_DisplayInfo() => "bindED Plugin\r\n\r\n2016 VoiceAttack.com\r\n20202024 alterNERDtive";
LoadBinds(Binds);
} /// <summary>
else if (context == "missingbinds") /// The plugins GUID, as required by the VoiceAttack plugin API.
{ /// </summary>
MissingBinds(Binds); /// <returns>The GUID.</returns>
} public static Guid VA_Id() => new ("{524B4B9A-3965-4045-A39A-A239BF6E2838}");
else
{ /// <summary>
LogWarn("Invoking the plugin with no context / a .binds file as context is deprecated and will be removed in a future version. Please invoke the 'loadbinds' context instead."); /// The Init method, as required by the VoiceAttack plugin API.
LogWarn("Bindings are also read automatically on VoiceAttack start and there should be no need to do it explicitly."); /// Runs when the plugin is initially loaded.
LoadBinds(Binds); /// </summary>
} /// <param name="vaProxy">The VoiceAttack proxy object.</param>
} public static void VA_Init1(dynamic vaProxy)
catch (Exception e) {
{ VA = vaProxy;
LogError(e.Message); VA.TextVariableChanged += new Action<string, string, string, Guid?>(TextVariableChanged);
} pluginPath = Path.GetDirectoryName(VA.PluginPath());
}
VA.SetText("bindED.version", VERSION.ToString());
public static void VA_StopCommand() { } VA.SetText("bindED.fork", "alterNERDtive");
public static void VA_Exit1(dynamic vaProxy) { } try
{
public static void TextVariableChanged(string name, string from, string to, Guid? internalID) LoadBinds(Binds);
{ }
if (name == "bindED.layout#") catch (Exception e)
{ {
LogInfo($"Keyboard layout changed to '{to}', reloading …"); LogError(e.Message);
Layout = to; }
try finally
{ {
LoadBinds(Binds); BindsWatcher.EnableRaisingEvents = true;
} MapWatcher.EnableRaisingEvents = true;
catch (Exception e) }
{ }
LogError(e.Message);
} /// <summary>
} /// The Invoke method, as required by the VoiceAttack plugin API.
} /// Runs whenever a plugin context is invoked.
/// </summary>
private static void LogError(string message) /// <param name="vaProxy">The VoiceAttack proxy object.</param>
{ public static void VA_Invoke1(dynamic vaProxy)
_VA!.WriteToLog($"ERROR | bindED: {message}", "red"); {
} VA = vaProxy;
try
private static void LogInfo(string message) {
{ string context = VA.Context.ToLower();
_VA!.WriteToLog($"INFO | bindED: {message}", "blue"); if (context == "diagnostics")
} {
LogInfo($"current keybord layout: {Layout}");
private static void LogWarn(string message) LogInfo($"current preset: {Preset}");
{ LogInfo($"detected binds file: {new FileInfo(DetectBindsFile(Preset!)).Name}");
_VA!.WriteToLog($"WARN | bindED: {message}", "yellow"); LogInfo($"detected binds file (full path): {DetectBindsFile(Preset!)}");
} }
else if (context == "listbinds")
public static void ListBinds(Dictionary<string, List<string>>? binds, string separator) {
{ ListBinds(Binds, VA.GetText("bindED.separator") ?? "\r\n");
_VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys)); }
LogInfo("List of Elite binds saved to TXT variable '~bindED.bindsList'."); else if (context == "loadbinds")
} {
// force reset everything
private static void LoadBinds(Dictionary<string, List<string>>? binds) Layout = null;
{ Preset = null;
foreach (KeyValuePair<string, List<string>> bind in binds!) if (!string.IsNullOrWhiteSpace(VA.GetText("~bindsFile")))
{ {
string value = string.Empty; Binds = ReadBinds(Path.Combine(BindingsDir, VA.GetText("~bindsFile")));
bool valid = true; }
if (bind.Value.Count == 0)
{ LoadBinds(Binds);
//LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …"); }
} else if (context == "missingbinds")
else {
{ MissingBinds(Binds);
foreach (string key in bind.Value) }
{ else
if (KeyMap!.ContainsKey(key)) {
{ if (string.IsNullOrWhiteSpace(context))
value += $"[{KeyMap[key]}]"; {
} LogError("Empty plugin context.");
else }
{ else
valid = false; {
LogError($"No valid key code for '{key}' found, skipping bind for '{bind.Key}' …"); LogError($"Invalid plugin context '{context}'.");
} }
}
if (valid) LogError("You generally do not need to invoke the plugin manually.");
{ }
_VA!.SetText(bind.Key, value); }
} catch (Exception e)
} {
} LogError(e.Message);
LogInfo($"Elite binds '{Preset}' for layout '{Layout}' loaded successfully."); }
} }
private static void MissingBinds(Dictionary<string, List<string>>? binds) /// <summary>
{ /// The Exit method, as required by the VoiceAttack plugin API.
List<string> missing = new List<string>(256); /// Runs when VoiceAttack is shut down.
foreach (KeyValuePair<string, List<string>> bind in binds!) /// </summary>
{ /// <param name="vaProxy">The VoiceAttack proxy object.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
if (bind.Value.Count == 0) public static void VA_Exit1(dynamic vaProxy)
{ {
missing.Add(bind.Key); }
}
} /// <summary>
if (missing.Count > 0) /// The StopCommand method, as required by the VoiceAttack plugin API.
{ /// Runs whenever all commands are stopped using the “Stop All Commands”
_VA!.SetText("~bindED.missingBinds", string.Join("\r\n", missing)); /// button or action.
_VA!.SetBoolean("~bindED.missingBinds", true); /// </summary>
LogInfo("List of missing Elite binds saved to TXT variable '~bindED.missingBinds'."); public static void VA_StopCommand()
} {
else }
{
LogInfo($"No missing keyboard binds found."); private static void TextVariableChanged(string name, string from, string to, Guid? internalID = null)
} {
} if (name == "bindED.layout#")
{
private static Dictionary<String, int> LoadKeyMap(string layout) LogInfo($"Keyboard layout changed to '{to}', reloading …");
{ Layout = to;
string mapFile = Path.Combine(_pluginPath, $"EDMap-{layout.ToLower()}.txt"); try
if (!File.Exists(mapFile)) {
{ LoadBinds(Binds);
throw new FileNotFoundException($"No map file for layout '{layout}' found."); }
} catch (Exception e)
Dictionary<string, int> map = new Dictionary<string, int>(256); {
foreach (String line in File.ReadAllLines(mapFile, System.Text.Encoding.UTF8)) LogError(e.Message);
{ }
String[] arItem = line.Split(";".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries); }
if ((arItem.Count() == 2) && (!String.IsNullOrWhiteSpace(arItem[0])) && (!map.ContainsKey(arItem[0]))) }
{
ushort iKey; private static void LogError(string message)
if (ushort.TryParse(arItem[1], out iKey)) {
{ VA!.WriteToLog($"ERROR | bindED: {message}", "red");
if (iKey > 0 && iKey < 256) }
map.Add(arItem[0].Trim(), iKey);
} private static void LogInfo(string message)
} {
} VA!.WriteToLog($"INFO | bindED: {message}", "blue");
if (map.Count == 0) }
{
throw new Exception($"Map file for {layout} does not contain any elements."); private static void LogWarn(string message)
} {
return map; VA!.WriteToLog($"WARN | bindED: {message}", "yellow");
} }
private static string DetectPreset() private static void ListBinds(Dictionary<string, List<string>>? binds, string separator)
{ {
string startFile = Path.Combine(_bindingsDir, "StartPreset.start"); VA!.SetText("~bindED.bindsList", string.Join(separator, binds!.Keys));
if (!File.Exists(startFile)) LogInfo("List of Elite binds saved to TXT variable '~bindED.bindsList'.");
{ }
throw new FileNotFoundException("No 'StartPreset.start' file found. Please run Elite: Dangerous at least once, then restart VoiceAttack.");
} private static void LoadBinds(Dictionary<string, List<string>>? binds)
return File.ReadAllText(startFile); {
} foreach (KeyValuePair<string, List<string>> bind in binds!)
{
private static string DetectBindsFile(string? preset) string value = string.Empty;
{ bool valid = true;
DirectoryInfo dirInfo = new DirectoryInfo(_bindingsDir); if (bind.Value.Count == 0)
FileInfo[] bindFiles = dirInfo.GetFiles().Where(i => Regex.Match(i.Name, $@"^{preset}(\.3\.0)?\.binds$").Success).OrderByDescending(p => p.LastWriteTime).ToArray(); {
// LogInfo($"No keyboard bind for '{bind.Key}' found, skipping …");
if (bindFiles.Count() == 0) }
{ else
throw new FileNotFoundException($"No bindings file found for preset '{preset}'. If this is a default preset, please change anything in Elites controls options."); {
} foreach (string key in bind.Value)
{
return bindFiles[0].FullName; if (KeyMap!.ContainsKey(key))
} {
value += $"[{KeyMap[key]}]";
private static Dictionary<string, List<string>> ReadBinds(string file) }
{ else
XElement rootElement; {
valid = false;
rootElement = XElement.Load(file); LogError($"No valid key code for '{key}' found, skipping bind for '{bind.Key}' …");
}
Dictionary<string, List<string>> binds = new Dictionary<string, List<string>>(512); }
if (rootElement != null)
{ if (valid)
foreach (XElement c in rootElement.Elements().Where(i => i.Elements().Count() > 0)) {
{ VA!.SetText(bind.Key, value);
List<string> keys = new List<string>(); }
foreach (var element in c.Elements().Where(i => i.HasAttributes)) }
{ }
if (element.Name == "Primary")
{ LogInfo($"Elite binds '{(string.IsNullOrWhiteSpace(VA!.GetText("~bindsFile")) ? Preset : VA!.GetText("~bindsFile"))}' for layout '{Layout}' loaded successfully.");
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")) private static void MissingBinds(Dictionary<string, List<string>>? binds)
{ {
keys.Add(modifier.Attribute("Key").Value); List<string> missing = new (256);
} foreach (KeyValuePair<string, List<string>> bind in binds!)
keys.Add(element.Attribute("Key").Value); {
} if (bind.Value.Count == 0)
} {
if (keys.Count == 0 && element.Name == "Secondary") //nothing found in primary... look in secondary missing.Add(bind.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")) if (missing.Count > 0)
{ {
keys.Add(modifier.Attribute("Key").Value); VA!.SetText("~bindED.missingBinds", string.Join("\r\n", missing));
} VA!.SetBoolean("~bindED.missingBinds", true);
keys.Add(element.Attribute("Key").Value); LogInfo("List of missing Elite binds saved to TXT variable '~bindED.missingBinds'.");
} }
} else
} {
binds.Add($"ed{c.Name.LocalName}", keys); LogInfo($"No missing keyboard binds found.");
} }
} }
return binds;
} private static Dictionary<string, int> LoadKeyMap(string layout)
{
private static void FileChangedHandler(string name) string mapFile = Path.Combine(pluginPath, $"EDMap-{layout.ToLower()}.txt");
{ if (!File.Exists(mapFile))
// so apparently these events all fire twice … lets make sure we only handle it once. {
if (_fileEventCount.ContainsKey(name)) throw new FileNotFoundException($"No map file for layout '{layout}' found.");
{ }
_fileEventCount[name] += 1;
} Dictionary<string, int> map = new (256);
else foreach (string line in File.ReadAllLines(mapFile, System.Text.Encoding.UTF8))
{ {
_fileEventCount.Add(name, 1); string[] arItem = line.Split(";".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries);
} if ((arItem.Count() == 2) && (!string.IsNullOrWhiteSpace(arItem[0])) && (!map.ContainsKey(arItem[0])))
if (_fileEventCount[name] % 2 == 0) {
{ ushort iKey;
try if (ushort.TryParse(arItem[1], out iKey))
{ {
// lets make semi-sure that the file isnt locked … if (iKey > 0 && iKey < 256)
// FIXXME: solve this properly {
Thread.Sleep(500); map.Add(arItem[0].Trim(), iKey);
// 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 }
// binds. }
if (name == $"EDMap-{Layout!.ToLower()}.txt")
{ if (map.Count == 0)
LogInfo($"Key map for layout '{Layout}' has changed, reloading …"); {
KeyMap = null; throw new Exception($"Map file for {layout} does not contain any elements.");
LoadBinds(Binds); }
}
else if (name == "StartPreset.start") return map;
{ }
LogInfo("Controls preset has changed, reloading …");
Preset = null; private static string DetectPreset()
LoadBinds(Binds); {
} string startFile = Path.Combine(BindingsDir, "StartPreset.4.start");
else if (Regex.Match(name, $@"{_preset}(\.3\.0)?\.binds$").Success) if (!File.Exists(startFile))
{ {
LogInfo($"Bindings file '{name}' has changed, reloading …"); startFile = Path.Combine(BindingsDir, "StartPreset.start");
Binds = null; if (!File.Exists(startFile))
LoadBinds(Binds); {
} throw new FileNotFoundException("No 'StartPreset.start' file found. Please run Elite: Dangerous at least once, then restart VoiceAttack.");
} }
catch (Exception e) }
{
LogError(e.Message); 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.");
} }
return presets.First();
}
private static string DetectBindsFile(string preset)
{
DirectoryInfo dirInfo = new (BindingsDir);
FileInfo[] bindFiles = dirInfo.GetFiles()
.Where(i => Regex.Match(i.Name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
.OrderByDescending(p => p.Name).ToArray();
if (bindFiles.Count() == 0)
{
bindFiles = dirInfo.GetFiles($"{preset}.binds");
if (bindFiles.Count() == 0)
{
throw new FileNotFoundException($"No bindings file found for preset '{preset}'. If this is a default preset, please change anything in Elites controls options.");
}
}
return bindFiles[0].FullName;
}
private static Dictionary<string, List<string>> ReadBinds(string file)
{
XElement rootElement;
rootElement = XElement.Load(file);
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 ();
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_"))
{
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_"))
{
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))
{
FileEventCount[name] += 1;
}
else
{
FileEventCount.Add(name, 1);
}
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
// binds.
if (name == $"EDMap-{Layout!.ToLower()}.txt")
{
LogInfo($"Key map for layout '{Layout}' has changed, reloading …");
KeyMap = null;
LoadBinds(Binds);
}
else if (name == "StartPreset.start")
{
LogInfo("Controls preset has changed, reloading …");
Preset = null;
LoadBinds(Binds);
}
else if (Regex.Match(name, $@"^{Regex.Escape(preset)}\.4\.\d\.binds$").Success)
{
LogInfo($"Bindings file '{name}' has changed, reloading …");
Binds = null;
LoadBinds(Binds);
}
}
catch (Exception e)
{
LogError(e.Message);
}
}
}
}
}

View file

@ -21,7 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion> <CodeAnalysisRuleSet />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -30,7 +30,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion> <CodeAnalysisRuleSet />
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -40,6 +40,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="bindED.cs" /> <Compile Include="bindED.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -54,6 +55,8 @@
</COMReference> </COMReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="docs\index.md" />
<Content Include=".gitignore" />
<Content Include="EDMap-de-neo2.txt"> <Content Include="EDMap-de-neo2.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -63,35 +66,32 @@
<Content Include="EDMap-en-us.txt"> <Content Include="EDMap-en-us.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="ReadMe.txt"> <None Include="docs\ReadMe.txt" />
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include=".editorconfig" /> <None Include=".editorconfig" />
<None Include="bindED-diagnostics-Profile.vap">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="bindED-reports-Profile.vap"> <None Include="bindED-reports-Profile.vap">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="CHANGELOG.md" /> <None Include="CHANGELOG.md" />
<None Include="docs\troubleshooting.md" />
<None Include="LICENSE"> <None Include="LICENSE">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="mkdocs.yml" />
<None Include="README.md"> <None Include="README.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <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. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release' ">
<ZipDirectory SourceDirectory="bin" DestinationFile="bindEDplugin.zip" Overwrite="true" />
</Target> </Target>
<Target Name="AfterBuild"> </Project>
</Target>
-->
</Project>

View file

@ -1,11 +1,18 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.30517.126 VisualStudioVersion = 17.3.32519.111
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bindEDplugin", "bindEDplugin.csproj", "{C8AC9134-639D-45D2-B5EF-138E0550E0C9}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bindEDplugin", "bindEDplugin.csproj", "{C8AC9134-639D-45D2-B5EF-138E0550E0C9}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC89314B-E504-4D5D-BB48-F1E3E4980A13}" 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -1,335 +1,335 @@
---------------------------------------------------------------- ----------------------------------------------------------------
About the bindED plugin - v1.0.0.1 (See end for version history) About the bindED plugin - v1.0.0.1 (See end for version history)
---------------------------------------------------------------- ----------------------------------------------------------------
The bindED plugin was created to copy the keyboard key bind information for Elite: Dangerous directly into VoiceAttack keypress variables. The variables The bindED plugin was created to copy the keyboard key bind information for Elite: Dangerous directly into VoiceAttack keypress variables. The variables
can then be used in conjunction with key press actions within VoiceAttack (the latest betas of VoiceAttack allow for keypresses based on variables). can then be used in conjunction with key press actions within VoiceAttack (the latest betas of VoiceAttack allow for keypresses based on variables).
The idea is to be able to change your key bindings from within Elite: Dangerous and have each change be reflected (semi-automatically) in VoiceAttack, The idea is to be able to change your key bindings from within Elite: Dangerous and have each change be reflected (semi-automatically) in VoiceAttack,
possibly saving some time and/or hair loss. possibly saving some time and/or hair loss.
Thanks to Lavaeolus in the VoiceAttack user forum for starting up the EDMap.txt layout, as well as all those that participate in making plugins all they Thanks to Lavaeolus in the VoiceAttack user forum for starting up the EDMap.txt layout, as well as all those that participate in making plugins all they
can be ;) can be ;)
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Setting up the bindED plugin Setting up the bindED plugin
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Copy the entire bindED folder from the zip file to the VoiceAttack Apps folder (the whole folder, not just the contents). The folder is usually located Copy the entire bindED folder from the zip file to the VoiceAttack Apps folder (the whole folder, not just the contents). The folder is usually located
in C:\Program Files (x86)\VoiceAttack (An installer may be created later to make this easier... for now it's just a zip ;)) in C:\Program Files (x86)\VoiceAttack (An installer may be created later to make this easier... for now it's just a zip ;))
For most, this is all you'll need to do. For others having trouble or want to explore, read on: For most, this is all you'll need to do. For others having trouble or want to explore, read on:
Inside the bindED folder, you will find the plugin file itself (bindED.dll), which uses VoiceAttack's Plugin v4 interface... that just means that you Inside the bindED folder, you will find the plugin file itself (bindED.dll), which uses VoiceAttack's Plugin v4 interface... that just means that you
will need VoiceAttack beta version v1.5.12.32 or later to use the plugin. will need VoiceAttack beta version v1.5.12.32 or later to use the plugin.
You will also find a file called, 'EDMap.txt'. If you open this text file, you will see that it contains each of the Elite: Dangerous key bind types associated You will also find a file called, 'EDMap.txt'. If you open this text file, you will see that it contains each of the Elite: Dangerous key bind types associated
with a numeric code. The code is simply the keyboard key code expressed as a number. For instance, the, 'A' key is 65, 'Z' is 90, 'Escape' is 27 and so on. with a numeric code. The code is simply the keyboard key code expressed as a number. For instance, the, 'A' key is 65, 'Z' is 90, 'Escape' is 27 and so on.
This is for English-US keyboards. Your keyboard layout may not line up exactly with this mapping, so adjustments may need to be made (you can edit this file This is for English-US keyboards. Your keyboard layout may not line up exactly with this mapping, so adjustments may need to be made (you can edit this file
as you see fit, just as long as it follows the KeyName;Code structure that you see inside the file). as you see fit, just as long as it follows the KeyName;Code structure that you see inside the file).
If you happen to create a layout that is different or find a mistake/omission in the English-US layout (this is new stuff, after all), please feel free to If you happen to create a layout that is different or find a mistake/omission in the English-US layout (this is new stuff, after all), please feel free to
upload your update to the VoiceAttack user forum for others to use: http://www.voiceattack.com/forum upload your update to the VoiceAttack user forum for others to use: http://www.voiceattack.com/forum
To see the list of key codes, go here: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx To see the list of key codes, go here: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
Note that the key codes are expressed in hexadecimal and will need to be converted to integer values. Note that the key codes are expressed in hexadecimal and will need to be converted to integer values.
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Using the bindED plugin Using the bindED plugin
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
To use the plugin, you will need to do a few things. To use the plugin, you will need to do a few things.
1) First, you will need to turn on plugin support in VoiceAttack. This is done from the Options screen. 1) First, you will need to turn on plugin support in VoiceAttack. This is done from the Options screen.
2) Next, you will need to create a command in VoiceAttack to call the bindED plugin which (optionally) includes the Elite: Dangerous binds file that you want to use. 2) Next, you will need to create a command in VoiceAttack to call the bindED plugin which (optionally) includes the Elite: Dangerous binds file that you want to use.
To do this, add a new command and then click on Other -> Advanced -> Execute an external plugin function. To do this, add a new command and then click on Other -> Advanced -> Execute an external plugin function.
3) In the new plugin action screen, select the, 'bindED' plugin from the Plugin list (bindED will be available if you had turned on plugin support and had installed 3) In the new plugin action screen, select the, 'bindED' plugin from the Plugin list (bindED will be available if you had turned on plugin support and had installed
the bindED plugin correctly). the bindED plugin correctly).
4) There are two routes you can take here. If you only have ONE binds file, and you want the bindED plugin to try to access your .binds file that is located 4) There are two routes you can take here. If you only have ONE binds file, and you want the bindED plugin to try to access your .binds file that is located
in C:\Users\YOUR_USER_NAME\AppData\Local\Frontier Developments\Elite Dangerous\Options\Bindings, simply leave the, 'Plugin Context' box blank. in C:\Users\YOUR_USER_NAME\AppData\Local\Frontier Developments\Elite Dangerous\Options\Bindings, simply leave the, 'Plugin Context' box blank.
The plugin will simply grab the NEWEST .binds file it finds in that directory and use it. The plugin will simply grab the NEWEST .binds file it finds in that directory and use it.
If you have MULTIPLE binds files for different situations, OR, the default behavior indicated above does not meet your need, you can specify which binds If you have MULTIPLE binds files for different situations, OR, the default behavior indicated above does not meet your need, you can specify which binds
file the bindED plugin is to access by typing or pasting the full path to the binds file that you want to use in the 'Plugin Context' box: file the bindED plugin is to access by typing or pasting the full path to the binds file that you want to use in the 'Plugin Context' box:
Selecting the, 'Wait for the plugin to fininsh...' option is up to you. Generally this should run very quicky, but if you have a situation where things need Selecting the, 'Wait for the plugin to fininsh...' option is up to you. Generally this should run very quicky, but if you have a situation where things need
to occur in a very specific order, select that option. to occur in a very specific order, select that option.
The full path is cut off in the image, but you get the idea ;) The full path is cut off in the image, but you get the idea ;)
5) Click OK, Done, etc. 5) Click OK, Done, etc.
The next time that this new command is executed (whether by itself to, 'reload' bindings or from a profile change) the VoiceAttack variables will be updated The next time that this new command is executed (whether by itself to, 'reload' bindings or from a profile change) the VoiceAttack variables will be updated
with what is indicated in the bind file. with what is indicated in the bind file.
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
How to use the keypress variables once they are updated How to use the keypress variables once they are updated
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
This is where it gets a little tricky. There are a lot of words here, but as you'll see, there's not a whole lot going on. This is where it gets a little tricky. There are a lot of words here, but as you'll see, there's not a whole lot going on.
First, you will need to be familiar with the Elite: Dangerous key binds in-game, as well as where those binds are stored in the configuration (.binds) files. First, you will need to be familiar with the Elite: Dangerous key binds in-game, as well as where those binds are stored in the configuration (.binds) files.
For this example, the landing gear toggle will be used. To toggle the landing gear, let's say the 'L' key is used in-game. For this example, the landing gear toggle will be used. To toggle the landing gear, let's say the 'L' key is used in-game.
The configuration file (.binds) will store that information in an xml element called, 'LandingGearToggle'. Inside the binds file, you will see the entry The configuration file (.binds) will store that information in an xml element called, 'LandingGearToggle'. Inside the binds file, you will see the entry
looks something like this: looks something like this:
<LandingGearToggle> <LandingGearToggle>
<Primary Device="Keyboard" Key="Key_L" /> <Primary Device="Keyboard" Key="Key_L" />
<Secondary Device="{NoDevice}" Key="" /> <Secondary Device="{NoDevice}" Key="" />
</LandingGearToggle> </LandingGearToggle>
When VoiceAttack encounters this element, it creates and a TEXT variable called, 'edLandingGearToggle'. It's just simply the element name with, 'ed' When VoiceAttack encounters this element, it creates and a TEXT variable called, 'edLandingGearToggle'. It's just simply the element name with, 'ed'
prefixed to it. Then, VoiceAttack notices that the key used is 'Key_L'. VoiceAttack gets the value indicated in the EDMap.txt file and puts it in the, prefixed to it. Then, VoiceAttack notices that the key used is 'Key_L'. VoiceAttack gets the value indicated in the EDMap.txt file and puts it in the,
'edLandingGearToggle' variable (you don't necessarily have to know this... I threw it in there in case you were wondering ;)) 'edLandingGearToggle' variable (you don't necessarily have to know this... I threw it in there in case you were wondering ;))
So, in VoiceAttack (later betas), you can have a key (or keys) pressed based on a variable. At the bottom of the keypress screen, you will see an input box So, in VoiceAttack (later betas), you can have a key (or keys) pressed based on a variable. At the bottom of the keypress screen, you will see an input box
that will allow you to specify what variable to use. Before, you would have, 'L' selected at the top of the screen. Now, you would just type in, that will allow you to specify what variable to use. Before, you would have, 'L' selected at the top of the screen. Now, you would just type in,
'edLandingGearToggle' (no quotes) in the variable input box. 'edLandingGearToggle' (no quotes) in the variable input box.
NOTE: A current list (well... my current list) of variables that can be affected are listed at the end of this file. NOTE: A current list (well... my current list) of variables that can be affected are listed at the end of this file.
Your command probably looks like this now: Your command probably looks like this now:
When I say... 'Landing Gear' When I say... 'Landing Gear'
Press L key and hold for 0.1 seconds and release Press L key and hold for 0.1 seconds and release
It would now look like this: It would now look like this:
When I say... 'Landing Gear' When I say... 'Landing Gear'
Press variable key(s) [edLandingGear] and hold for 0.1 seconds and release Press variable key(s) [edLandingGear] and hold for 0.1 seconds and release
If anybody out there wants to create an Elite: Dangerous variable mapping to what is seen in-game, that would be really cool ;) If anybody out there wants to create an Elite: Dangerous variable mapping to what is seen in-game, that would be really cool ;)
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Advanced stuff and things to consider for those who want even more confusion. Advanced stuff and things to consider for those who want even more confusion.
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Note that you can have multiple plugin actions to load several (possibly custom) bind files at the same time if you need to. Each subsequently loaded bind file Note that you can have multiple plugin actions to load several (possibly custom) bind files at the same time if you need to. Each subsequently loaded bind file
will override the previous ones. You can also do this with one action by separating the bind paths with a semicolon. You may never need to do this, but it's will override the previous ones. You can also do this with one action by separating the bind paths with a semicolon. You may never need to do this, but it's
there if you want to do something with it. there if you want to do something with it.
Way off the chart... Something else to try that is left over from testing (and left in for anybody that wants to use it) is if you want to load a Way off the chart... Something else to try that is left over from testing (and left in for anybody that wants to use it) is if you want to load a
binds file on plugin initialization (that is, when the plugin first loads (when VA starts up)... not when the plugin is invoked), create shortcuts to binds file on plugin initialization (that is, when the plugin first loads (when VA starts up)... not when the plugin is invoked), create shortcuts to
the binds files and place them in the same folder as the plugin .dll. Each shortcut is processed in alphabetical order. This is one way to have an the binds files and place them in the same folder as the plugin .dll. Each shortcut is processed in alphabetical order. This is one way to have an
automatic, global initialization of variables that do not require the plugin to be invoked. If this doesn't make any sense to you, do not worry ;) automatic, global initialization of variables that do not require the plugin to be invoked. If this doesn't make any sense to you, do not worry ;)
If you are going to be creating profiles for others, it might be a good idea to put some extra steps in your commands in case your users do not have If you are going to be creating profiles for others, it might be a good idea to put some extra steps in your commands in case your users do not have
the bindED plugin (either not installed, not configured properly or not initialized). Going to expand on the whole landing gear thing again in this example... the bindED plugin (either not installed, not configured properly or not initialized). Going to expand on the whole landing gear thing again in this example...
Begin Text Compare : [edLandingGearToggle] Has Been Set Begin Text Compare : [edLandingGearToggle] Has Been Set
Press variable key(s) [edLandingGearToggle] and hold for 0.1 seconds and release Press variable key(s) [edLandingGearToggle] and hold for 0.1 seconds and release
Else Else
Press L key and hold for 0.1 seconds and release Press L key and hold for 0.1 seconds and release
Write '[Orange] Variable keypress not set. Using default keypress.' to log Write '[Orange] Variable keypress not set. Using default keypress.' to log
End Condition End Condition
Note that if the variable HAS BEEN SET (not null), the keypress uses the variable. If the variable has not been set, you can have a fall-back keypress, or, Note that if the variable HAS BEEN SET (not null), the keypress uses the variable. If the variable has not been set, you can have a fall-back keypress, or,
just show a warning. just show a warning.
One more thing... if you happen to have a '.binds' file for Star Citizen that has a good variety of key/key combinations (with modifiers... ctrl/alt/shift/win), One more thing... if you happen to have a '.binds' file for Star Citizen that has a good variety of key/key combinations (with modifiers... ctrl/alt/shift/win),
please let me know and I will create a bindSC ;) please let me know and I will create a bindSC ;)
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Variable List Reference Variable List Reference
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Below is a current list of the variables that bindED can update (and you can use in your keypress actions). Remember, the element within the .binds file is Below is a current list of the variables that bindED can update (and you can use in your keypress actions). Remember, the element within the .binds file is
whatever the variable name is below, minus the, 'ed' prefix. So, 'edAutoBreakBuggyButton' below correlates to the 'AutoBreakBuggyButton' element in the .binds whatever the variable name is below, minus the, 'ed' prefix. So, 'edAutoBreakBuggyButton' below correlates to the 'AutoBreakBuggyButton' element in the .binds
file. Note that if no keys are set for the particular element, the variable value will be null (Not Set). This list can change at any time depending file. Note that if no keys are set for the particular element, the variable value will be null (Not Set). This list can change at any time depending
on Frontier, and is only here to give you an idea of what's currently available without having to dig into your .binds file. Good luck, Captain. ;) on Frontier, and is only here to give you an idea of what's currently available without having to dig into your .binds file. Good luck, Captain. ;)
edAutoBreakBuggyButton edAutoBreakBuggyButton
edBackwardKey edBackwardKey
edBackwardThrustButton edBackwardThrustButton
edBackwardThrustButton_Landing edBackwardThrustButton_Landing
edBuggyPitchDownButton edBuggyPitchDownButton
edBuggyPitchUpButton edBuggyPitchUpButton
edBuggyPrimaryFireButton edBuggyPrimaryFireButton
edBuggyRollLeftButton edBuggyRollLeftButton
edBuggyRollRightButton edBuggyRollRightButton
edBuggySecondaryFireButton edBuggySecondaryFireButton
edBuggyToggleReverseThrottleInput edBuggyToggleReverseThrottleInput
edBuggyTurretPitchDownButton edBuggyTurretPitchDownButton
edBuggyTurretPitchUpButton edBuggyTurretPitchUpButton
edBuggyTurretYawLeftButton edBuggyTurretYawLeftButton
edBuggyTurretYawRightButton edBuggyTurretYawRightButton
edCamPitchDown edCamPitchDown
edCamPitchUp edCamPitchUp
edCamTranslateBackward edCamTranslateBackward
edCamTranslateDown edCamTranslateDown
edCamTranslateForward edCamTranslateForward
edCamTranslateLeft edCamTranslateLeft
edCamTranslateRight edCamTranslateRight
edCamTranslateUp edCamTranslateUp
edCamTranslateZHold edCamTranslateZHold
edCamYawLeft edCamYawLeft
edCamYawRight edCamYawRight
edCamZoomIn edCamZoomIn
edCamZoomOut edCamZoomOut
edChargeECM edChargeECM
edCycleFireGroupNext edCycleFireGroupNext
edCycleFireGroupPrevious edCycleFireGroupPrevious
edCycleNextHostileTarget edCycleNextHostileTarget
edCycleNextPanel edCycleNextPanel
edCycleNextSubsystem edCycleNextSubsystem
edCycleNextTarget edCycleNextTarget
edCyclePreviousHostileTarget edCyclePreviousHostileTarget
edCyclePreviousPanel edCyclePreviousPanel
edCyclePreviousSubsystem edCyclePreviousSubsystem
edCyclePreviousTarget edCyclePreviousTarget
edDecreaseSpeedButtonMax edDecreaseSpeedButtonMax
edDeployHardpointToggle edDeployHardpointToggle
edDeployHeatSink edDeployHeatSink
edDisableRotationCorrectToggle edDisableRotationCorrectToggle
edDownThrustButton edDownThrustButton
edDownThrustButton_Landing edDownThrustButton_Landing
edEjectAllCargo edEjectAllCargo
edEjectAllCargo_Buggy edEjectAllCargo_Buggy
edEngineColourToggle edEngineColourToggle
edFireChaffLauncher edFireChaffLauncher
edFocusCommsPanel edFocusCommsPanel
edFocusCommsPanel_Buggy edFocusCommsPanel_Buggy
edFocusLeftPanel edFocusLeftPanel
edFocusLeftPanel_Buggy edFocusLeftPanel_Buggy
edFocusRadarPanel edFocusRadarPanel
edFocusRadarPanel_Buggy edFocusRadarPanel_Buggy
edFocusRightPanel edFocusRightPanel
edFocusRightPanel_Buggy edFocusRightPanel_Buggy
edForwardKey edForwardKey
edForwardThrustButton edForwardThrustButton
edForwardThrustButton_Landing edForwardThrustButton_Landing
edGalaxyMapOpen edGalaxyMapOpen
edGalaxyMapOpen_Buggy edGalaxyMapOpen_Buggy
edHeadlightsBuggyButton edHeadlightsBuggyButton
edHeadLookPitchDown edHeadLookPitchDown
edHeadLookPitchUp edHeadLookPitchUp
edHeadLookReset edHeadLookReset
edHeadLookToggle edHeadLookToggle
edHeadLookToggle_Buggy edHeadLookToggle_Buggy
edHeadLookYawLeft edHeadLookYawLeft
edHeadLookYawRight edHeadLookYawRight
edHMDReset edHMDReset
edHyperspace edHyperspace
edHyperSuperCombination edHyperSuperCombination
edIncreaseEnginesPower edIncreaseEnginesPower
edIncreaseEnginesPower_Buggy edIncreaseEnginesPower_Buggy
edIncreaseSpeedButtonMax edIncreaseSpeedButtonMax
edIncreaseSystemsPower edIncreaseSystemsPower
edIncreaseSystemsPower_Buggy edIncreaseSystemsPower_Buggy
edIncreaseWeaponsPower edIncreaseWeaponsPower
edIncreaseWeaponsPower_Buggy edIncreaseWeaponsPower_Buggy
edLandingGearToggle edLandingGearToggle
edLeftThrustButton edLeftThrustButton
edLeftThrustButton_Landing edLeftThrustButton_Landing
edMicrophoneMute edMicrophoneMute
edMouseReset edMouseReset
edOpenOrders edOpenOrders
edOrbitLinesToggle edOrbitLinesToggle
edOrderAggressiveBehaviour edOrderAggressiveBehaviour
edOrderDefensiveBehaviour edOrderDefensiveBehaviour
edOrderFocusTarget edOrderFocusTarget
edOrderFollow edOrderFollow
edOrderHoldFire edOrderHoldFire
edOrderHoldPosition edOrderHoldPosition
edOrderRequestDock edOrderRequestDock
edPause edPause
edPhotoCameraToggle edPhotoCameraToggle
edPhotoCameraToggle_Buggy edPhotoCameraToggle_Buggy
edPitchDownButton edPitchDownButton
edPitchDownButton_Landing edPitchDownButton_Landing
edPitchUpButton edPitchUpButton
edPitchUpButton_Landing edPitchUpButton_Landing
edPrimaryFire edPrimaryFire
edQuickCommsPanel edQuickCommsPanel
edQuickCommsPanel_Buggy edQuickCommsPanel_Buggy
edRadarDecreaseRange edRadarDecreaseRange
edRadarIncreaseRange edRadarIncreaseRange
edRecallDismissShip edRecallDismissShip
edResetPowerDistribution edResetPowerDistribution
edResetPowerDistribution_Buggy edResetPowerDistribution_Buggy
edRightThrustButton edRightThrustButton
edRightThrustButton_Landing edRightThrustButton_Landing
edRollLeftButton edRollLeftButton
edRollLeftButton_Landing edRollLeftButton_Landing
edRollRightButton edRollRightButton
edRollRightButton_Landing edRollRightButton_Landing
edSecondaryFire edSecondaryFire
edSelectHighestThreat edSelectHighestThreat
edSelectTarget edSelectTarget
edSelectTarget_Buggy edSelectTarget_Buggy
edSelectTargetsTarget edSelectTargetsTarget
edSetSpeed100 edSetSpeed100
edSetSpeed25 edSetSpeed25
edSetSpeed50 edSetSpeed50
edSetSpeed75 edSetSpeed75
edSetSpeedMinus100 edSetSpeedMinus100
edSetSpeedMinus25 edSetSpeedMinus25
edSetSpeedMinus50 edSetSpeedMinus50
edSetSpeedMinus75 edSetSpeedMinus75
edSetSpeedZero edSetSpeedZero
edShipSpotLightToggle edShipSpotLightToggle
edShowPGScoreSummaryInput edShowPGScoreSummaryInput
edSteerLeftButton edSteerLeftButton
edSteerRightButton edSteerRightButton
edSupercruise edSupercruise
edSystemMapOpen edSystemMapOpen
edSystemMapOpen_Buggy edSystemMapOpen_Buggy
edTargetNextRouteSystem edTargetNextRouteSystem
edTargetWingman0 edTargetWingman0
edTargetWingman1 edTargetWingman1
edTargetWingman2 edTargetWingman2
edToggleBuggyTurretButton edToggleBuggyTurretButton
edToggleButtonUpInput edToggleButtonUpInput
edToggleCargoScoop edToggleCargoScoop
edToggleCargoScoop_Buggy edToggleCargoScoop_Buggy
edToggleDriveAssist edToggleDriveAssist
edToggleFlightAssist edToggleFlightAssist
edToggleReverseThrottleInput edToggleReverseThrottleInput
edUI_Back edUI_Back
edUI_Down edUI_Down
edUI_Left edUI_Left
edUI_Right edUI_Right
edUI_Select edUI_Select
edUI_Toggle edUI_Toggle
edUI_Up edUI_Up
edUIFocus edUIFocus
edUIFocus_Buggy edUIFocus_Buggy
edUpThrustButton edUpThrustButton
edUpThrustButton_Landing edUpThrustButton_Landing
edUseAlternateFlightValuesToggle edUseAlternateFlightValuesToggle
edUseBoostJuice edUseBoostJuice
edUseShieldCell edUseShieldCell
edVerticalThrustersButton edVerticalThrustersButton
edWeaponColourToggle edWeaponColourToggle
edWingNavLock edWingNavLock
edYawLeftButton edYawLeftButton
edYawLeftButton_Landing edYawLeftButton_Landing
edYawRightButton edYawRightButton
edYawRightButton_Landing edYawRightButton_Landing
edYawToRollButton edYawToRollButton
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
Version History Version History
-------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
v1.0 - Initial release v1.0 - Initial release
v1.0.0.1 - Fixed file sorting issue when accessing .binds files. v1.0.0.1 - Fixed file sorting issue when accessing .binds files.

BIN
docs/images/keypress.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

77
docs/index.md Normal file
View file

@ -0,0 +1,77 @@
# bindED
This VoiceAttack plugin reads keybindings in Elite:Dangerous and stores them as
VoiceAttack variables. It was originally written by Gary (the developer of
VoiceAttack) [and published on the VoiceAttack
forums](https://forum.voiceattack.com/smf/index.php?topic=564.0). You can find
the [original README here](ReadMe.txt) for reference.
I have basically done a complete rewrite the original source code at this point
and added a lot of features including automatic detection of the correct
bindings file and support for non-US keyboard layouts (see below for details).
## Installing
Grab `bindEDplugin.zip` from the [release
page](https://github.com/alterNERDtive/bindED/releases/latest) and extract it
into your VoiceAttacks `Apps` directory. If you have an older version already
installed, please delete the `bindED` subfolder first.
For Horizons players, thats it! When VoiceAttack loads, bindED will
automatically detect your bindings. It will also keep a watchful eye on Elites
bindings folder and reload them when there is a change!
For Odyssey players, there is an additional caveat: you have to use the same
preset for all 4 sections (general, ship, SRV, foot). Sadly its not apparent
from the files which of these sections a bind belongs to, so there is no simple
way to read multiple files properly.
## Using Binds In Commands
Each bind in Elite Dangerous has a name assigned in the binds file. The plugin
takes this name, prepends `ed` and turns it into a VoiceAttack variable.
To press the corresponding key in the game, you will have to create a new
`Key Press` action in a VoiceAttack command and use the desired variable named
after the ingame bind. In order to toggle your landing gear for example you will
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:
* en-US
* en-GB
* de-neo2
If you are on a different layout, you can either use only the keys that work
with the default layout or add support for your preferred layout yourself. See
[[Troubleshooting#Adding a Keyboard Layout]].
## Need Help / Want to Contribute?
If you run into any errors have a look at [[Troubleshooting]]. If your problem
persists, please [file an
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)

121
docs/troubleshooting.md Normal file
View file

@ -0,0 +1,121 @@
# Troubleshooting
## Diagnosing Plugin Issues
If something goes awry you can manually call the `loadbinds` plugin context or
restart VoiceAttack to force a refresh.
If that doesnt help and your problems persist the first step should be to
import and load the included `bindED-diagnostics` profile from the plugin
directory. It will output the current plugin state (keyboard layout, preset and
binds file used) to the VoiceAttack log. You should also include the output of
that if you file an issue or ask for help on Discord.
If that seems correct, theres the `bindED-reports` profile. It will generate
both a list of bind names used by Elite and a report of binds that do not have a
keyboard shortcut assigned, and put them on your Desktop. Note that those lists
are currently not filtered in any way and will contain _all_ binds regardless if
they are used in any profiles or if they can even have a keyboard key assigned
(e.g. axes).
## Migrating from the Old Plugin
If you use this as a drop-in replacement for the initial version of bindED made
by Gary all commands invoking the plugin will throw an error message. Gary has
asked me to change the plugins GUID, and the plugin with the old one will no
longer be found. _That is irrelevant in basically all cases and can safely be
ignored_.
Binds will be read automatically when VoiceAttack starts and whenever they
change. To get rid of the error message(s) remove the outdated plugin
invocation(s) from your commands.
## Make Sure You Actually Have Binds Files
Before starting VoiceAttack with the plugin installed, you need to load the game
at least once! That will create the directory structure the plugin is going to
read from. You also need to have changed _any_ key in controls options.
If you start Elite for the first time with VoiceAttack already running you will
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 support has been dropped. If you still play legacy, you will have to
use bindED <5.0.
## Specifying a Binds File to Load
You can set the text variable `~bindsFile` to a specific file name (e.g.
`custom.3.0.binds`) before executing the `loadbinds` context to have that
specific binds file loaded.
Make sure to only use the _file name_ of an existing binds file, do _not_
specify the full path.
This should be a last resort effort for when the game introduces changes that
break the plugins auto detection.
## Adding a Keyboard Layout
If you are using any non-US layout you might have noticed that some binds dont
work. The game itself supports a certain range of “standard” keyboard layouts
and falls back to UK QWERTY if you are using something else. The original plugin
was made for the US keyboard layout. So if you are using a layout that is not
natively supported by Elite and/or that has keys that dont exist on the US
layout, some keys will not work out of the box, e.g.:
* “VoiceAttack presses `p` but the game thinks its `v`!” (layout not natively
supported by Elite)
* “VoiceAttack presses `ß` but nothing happens!” (key not in the US key map)
I have included a map file for [Neo2](https://neo-layout.org)
(`EDMap-de-neo2.txt`) which is the layout that I am using personally and
A.Cyprus was kind enough to provide full support for en-GB. If you are on a
different layout, you will have to create a corresponding map file yourself. But
if you do Ill be happy to include it in future releases!
To add support for a new keyboard layout, you will have to create the
corresponding `EDMap-<id>.txt` file. Start by copying `EDMap-en-us.txt` in the
plugin directory and renaming it appropriately. The file contains a list of
`sym:code` pairs, one per line. The `sym` part is exactly like it is listed in
Elites `.binds` files, the `code` is the corresponding keycode VoiceAttack is
going to send.
You will have to go through the entire list and replace the existing `code`s
with the right ones for your layout. In order to do so I recommend following
these steps:
1. Open a reference for the UK QWERTY layout, [e.g. Wikipedias
image](https://en.wikipedia.org/wiki/QWERTY#/media/File:KB_United_Kingdom.svg).
1. Get a tool that can display keycodes for keys you press, [e.g. NirSofts
KeyboardStateView](https://www.nirsoft.net/utils/keyboard_state_view.html).
Or anything else that does it. Make sure it shows you _decimal_ keycodes
instead of (only) hex!
1. Make sure your PC is actually set to the layout you want to add.
1. Go through the file one by one.
1. Take note of its position on the keyboard. In this example we are using
the one in the top left next to `1` and my layout Neo2.
1. Find the correct key on the UK reference, for this example “Key_Grave”.
1. Press the corresponding key on your keyboard and observe the keycode it
produces. For Neo2 that was 186.
1. Change the `code` in the line to the correct keycode. For Neo2 the line
reads “Key_Grave:186”.
1. Repeat for the next line.
There are a bunch of symbols in the file that arent on a standard keyboard,
just ignore those. If you have extra keys that are not on the UK QWERTY layout,
youll have to go into the games controls options and try binding those keys.
If it works take the symbols they produce and go back to step 4 with them.
For the actual _letters_ youll have to assign the keycode of the key you are
pressing to the “Key_X” that is displayed ingame. So if the ingame controls
options say youve just pressed “H”, replace the “Key_H” line with the keycode
you get when you press the key you bound ingame. If the game assigns the letters
that are actually in that place on your layout, you wont have to do anything
for these.
Once you have tested and confirmed everything working, feel free to open a pull
request or issue and Ill include the new map!

26
mkdocs.yml Normal file
View file

@ -0,0 +1,26 @@
site_name: "bindED VoiceAttack plugin"
site_url: https://alterNERDtive.github.io/bindED
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"
theme:
name: readthedocs
prev_next_buttons_location: both
plugins:
- search
- roamlinks
markdown_extensions:
- toc:
permalink: True
- sane_lists
nav:
- 'index.md'
- 'troubleshooting.md'
- '⎋ Changelog': 'https://github.com/alterNERDtive/bindED/blob/release/CHANGELOG.md'
- '⎋ Report a Bug': 'https://github.com/alterNERDtive/bindED/issues/'

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"
}
}
}
}